浅析 GO 中的内存对齐

浅析 GO 中的内存对齐 前置概念 位(bit) ​ 所谓位,是最基本的概念,在计算机中,由于只有逻辑0和逻辑1的存在,因此很多东西、动作、数字都要表示为一串二进制的字码。例如: 1001 0000 1101等等。其中每一个逻辑0或者1便是一个位。例如这个例子里的1000 1110共有八个位,它的英文名字叫(bit),是计算机中最基本的单位。 字节(Byte) ​ 由八个位(bit)组成的一个单元,也就是8个bit组成1个Byte。在计算机科学中,用于表示ASCII字符,便是运用字节来记录表示字母和一些符号,例如字符A便用 “0100 0001”来表示。 字(word) 表示被处理信息的单位,用来度量数据类型的宽度。 ​ 在计算机体系结构中,“字"是处理器可以在单个操作中处理的数据单元 - 通常是内存中可寻址的最小单位。它是固定大小的比特(二进制数字)组。处理器的字长决定了它处理数据的效率。常见的字长包括 8、16、32 和 64 比特。一些计算机处理器体系结构支持半字,即一个字中的一半比特数,以及双字,即两个相邻字。 ​ 现在最常见的架构是 32 位和 64 位。如果你有 32 位处理器,那意味着它可以一次访问 4 个字节,也就是字长为 4 个字节。如果你有 64 位处理器,那意味着它可以一次访问 8 个字节,也就是字长为 8 个字节。 ​ 将数据存储在内存中时,每个 32 位数据字都有一个唯一地址,如下所示。 Figure. 1 - 可寻址内存 ​ 我们可以使用加载字(lw)指令读取存储在内存中的数据并将其加载到一个寄存器中。 ​ 字的位数并不是确定值,如 x86 机器将字定义为16位(汇编语言课程中),也就是两个字节,在32位arm机器中,字定义为32位(嵌入式课程中)。 ​ 指令字长:字节的整数倍,指一个指令字中包含的二进制代码位数。 ​ 存储字长:字节的整数倍,一个存储单元存储的二进制代码的长度。 字是单位,随系统而变,字长是同一时间处理二进制的长度。 字符与字节对应关系 ​ 常见的编码字符与字节的对应关系如下: ① ASCII 码中,一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数,换算为十进制。最小值0,最大值255。 ② UTF-8 编码中,一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。 ③ Unicode 编码中,一个英文等于两个字节,一个中文(含繁体)等于两个字节。 符号:英文标点占一个字节,中文标点占两个字节。举例:英文句号“.”占1个字节的大小,中文句号“。”占2个字节的大小。 ④ GBK 编码方式是中文占两个字节,英文占1个字节。 ...

2024-12-12 · 6 min · 2813 words · Luenci

opentelemetry 入门

opentelemetry 入门 概念 ​ OpenTelemetry 是一个可观测性框架和工具包, 旨在创建和管理遥测数据,如链路、 指标和日志。 重要的是,OpenTelemetry 是供应商和工具无关的,这意味着它可以与各种可观测性后端一起使用, 包括 Jaeger 和 Prometheus 这类开源工具以及商业化产品。 ​ OpenTelemetry 不是像 Jaeger、Prometheus 或其他商业供应商那样的可观测性后端。 OpenTelemetry 专注于遥测数据的生成、采集、管理和导出。 ​ OpenTelemetry 的一个主要目标是, 无论应用程序或系统采用何种编程语言、基础设施或运行时环境,你都可以轻松地将其仪表化。 重要的是,遥测数据的存储和可视化是有意留给其他工具处理的。 Traces ​ 获取或初始化一个追踪器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package main import ( "context" "log" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.19.0" "go.opentelemetry.io/otel/trace" ) var tracer trace.Tracer func newExporter(ctx context.Context) (sdktrace.SpanExporter, error) { // Your preferred exporter: console, jaeger, zipkin, OTLP, etc. return stdouttrace.New(stdouttrace.WithPrettyPrint()) } func newTraceProvider(exp sdktrace.SpanExporter) *sdktrace.TracerProvider { // Ensure default SDK resources and the required service name are set. r, err := resource.Merge( resource.Default(), resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName("ExampleService"), ), ) if err != nil { panic(err) } return sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp), sdktrace.WithResource(r), ) } func main() { ctx := context.Background() exp, err := newExporter(ctx) if err != nil { log.Fatalf("failed to initialize exporter: %v", err) } // Create a new tracer provider with a batch span processor and the given exporter. tp := newTraceProvider(exp) // Handle shutdown properly so nothing leaks. defer func() { _ = tp.Shutdown(ctx) }() otel.SetTracerProvider(tp) // Finally, set the tracer that can be used for this package. tracer = tp.Tracer("example.io/package/name") } ...

2024-11-15 · 7 min · 3117 words · Luenci

golang slice 并发写入

golang slice 并发写入 ​ 由于 slice/map 是引用类型,golang 函数是传值调用,所用参数副本依然是原来的 slice, 并发访问同一个资源会导致竟态条件。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package main import ( "fmt" "sync" ) func main() { var ( slc = []int{} n = 10 wg sync.WaitGroup ) wg.Add(n) for i := 0; i < n; i++ { go func() { slc = append(slc, i) wg.Done() }() } wg.Wait() fmt.Println("len:", len(slc)) fmt.Println("done") } (每次执行都会有不同的结果) out: len: 3 done ...

2024-10-25 · 2 min · 728 words · Luenci

Golang pprof 性能分析指南

Golang pprof 性能分析指南 pprof 是一个用于可视化和分析分析数据的工具。 采样方式 方式名称 如何使用 优点 缺点 使用场景 runtime/pprof 手动调用【runtime.StartCPUProfile、runtime.SweightCPUProfile】等API来进行数据的采集。采集程序(非 Server)的指定区块的运行数据进行分析。 灵活性高、按需采集。 工具型应用(比如说定制化的分析小工具、集成到公司监控系统)。这种应用运行一段时间就结束。 net/http/pprof 通过http服务来获取Profile采样文件。 import _ "net/http/pprof"。基于 HTTP Server 运行,并且可以采集运行时数据进行分析。net/http/pprof中只是使用runtime/pprof包来进行封装了一下,并在http端口上暴露出来 简单易用 在线服务(一直运行着的程序) go test 通过命令go test -bench . -cpuprofile cpu.prof来进行采集数据。 针对性强、细化到函数 进行某函数的性能测试 指标解释 常用指标如下: allocs:所有时刻的内存使用情况,包括正在使用的及已经回收的 block:导致在同步原语上发生阻塞的堆栈跟踪 cmdline: 当前程序的命令行的完整调用路径。 goroutine:目前的 goroutine 数量及运行情况 heap:当前时刻的内存使用情况 mutex:查看导致互斥锁的竞争持有者的堆栈跟踪 profile:默认进行 30s 的 CPU Profiling,得到一个分析用的 profile 文件 threadcreate:查看创建新 OS 线程的堆栈跟踪。 trance:当前程序执行的追踪,可以在秒数的 GET 参数中指定持续时间。在获取追踪文件后,请使用 go 工具的 trace 命令来调查追踪。(深入浅出 Go trace (qq.com)) 注意,默认情况下是不追踪block和mutex的信息的,如果想要看这两个信息,需要在代码中加上两行: 1 2 runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪,block runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪,mutex 注意,上文的所有信息都是实时的,如果你刷新一下,是可以看到数字在变化的。此时如果点击蓝色的连接,可以看到一些协程的栈信息,这些信息并不容易阅读。如果想要更加清晰的数据,需要将信息保存下来,在本地进行分析。 ...

2024-08-03 · 7 min · 3029 words · Luenci

声明式和命令式编程

声明式和命令式编程 声明式(declarative)是结果导向的,命令式(imperative)是过程导向的 命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。 声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。 声明式编程和命令式编程的代码例子 ​ 举个简单的例子,假设我们想让一个数组里的数值翻倍。 我们用命令式编程风格实现,像下面这样: 1 2 3 4 5 6 7 8 9 10 11 var numbers = [1,2,3,4,5] var doubled = [] for(var i = 0; i < numbers.length; i++) { var newNumber = numbers[i] * 2 doubled.push(newNumber) } console.log(doubled) //=> [2,4,6,8,10] ​ 我们直接遍历整个数组,取出每个元素,乘以二,然后把翻倍后的值放入新数组,每次都要操作这个双倍数组,直到计算完所有元素。 而使用声明式编程方法,我们可以用 Array.map 函数,像下面这样: 1 2 3 4 5 6 7 var numbers = [1,2,3,4,5] var doubled = numbers.map(function(n) { return n * 2 }) console.log(doubled) //=> [2,4,6,8,10] ​ map 利用当前的数组创建了一个新数组,新数组里的每个元素都是经过了传入map的函数(这里是function(n) { return n*2 })的处理。 ​ map函数所作的事情是将直接遍历整个数组的过程归纳抽离出来,让我们专注于描述我们想要的是什么(what)。注意,我们传入map的是一个纯函数;它不具有任何副作用(不会改变外部状态),它只是接收一个数字,返回乘以二后的值。 ​ 在一些具有函数式编程特征的语言里,对于list数据类型的操作,还有一些其他常用的声明式的函数方法。例如,求一个list里所有值的和,命令式编程会这样做: 1 2 3 4 5 6 7 8 9 var numbers = [1,2,3,4,5] var total = 0 for(var i = 0; i < numbers.length; i++) { total += numbers[i] } console.log(total) //=> 15 而在声明式编程方式里,我们使用 reduce 函数: 1 2 3 4 5 6 7 var numbers = [1,2,3,4,5] var total = numbers.reduce(function(sum, n) { return sum + n }); console.log(total) //=> 15 ​ reduce 函数利用传入的函数把一个 list 运算成一个值。它以这个函数为参数,数组里的每个元素都要经过它的处理。每一次调用,第一个参数(这里是sum)都是这个函数处理前一个值时返回的结果,而第二个参数(n)就是当前元素。这样下来,每此处理的新元素都会合计到sum中,最终我们得到的是整个数组的和。 ​ 同样,reduce 函数归纳抽离了我们如何遍历数组和状态管理部分的实现,提供给我们一个通用的方式来把一个 list 合并成一个值。我们需要做的只是指明我们想要的是什么 ...

2024-07-03 · 7 min · 3491 words · Luenci

golang的coredump调试入门

core dump 概念 ​ core dump文件实际上是进程在某个时间点时的内存映像,当时进程使用的内存是啥样就会被原样保存下来存在文件系统的某个位置上,这个时间点一般是触发了SIGSEGV或者SIGABRT这两个信号的时候,当进程的内存映像保存完毕后进程就会异常终止,也就是大家喜闻乐见的“程序崩了”和“段错误:核心已转储”。 ​ 因此 coredump 就像是程序出错崩溃后的“第一现场”,是用来排查错误的主要资源。 golang 程序生成 coredump 方法 设置环境变量和在代码里调用相关的标准库接口 ​ 在这之前先用ulimit命令检测下系统当前能不能生成coredump: 1 2 $ ulimit -c unlimited ​ 如果是unlimited就表示可以,如果是0那就不会生成,需要修改ulimit的设置。 修改GOTRACEBACK环境变量 ​ GOTRACEBACK是用来控制panic发生时golang程序行为的,值是字符串,具体内容如下: 值 行为 none 不打印任何堆栈跟踪信息,不过崩溃的原因和哪行代码触发的panic还是会打印 single 只打印当前正在运行的触发panic的goroutine的堆栈以及runtime的堆栈;如果panic是runtime里发出的,则打印所有goroutine的堆栈跟踪信息 all 打印所有用户创建的goroutine的堆栈信息(不包含runtime的) system 在前面all的基础上把runtime相关的所有协程的堆栈信息也一起打印出来 crash 打印的内容和前面system一样,但还会额外生成对应操作系统上的 coredump 文件 ​ 将这个环境变量设置成crash就可以获得信息最全面的coredump文件。 设置编译参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main import ( "fmt" "math/rand" ) func main() { arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} for { index := rand.Intn(11) fmt.Println(arr[index]) } } 1 go build -gcflags="all=-N -l" main.go ...

4 min · 1511 words · Luenci

我的编程、热爱、生活

我的编程、热爱、生活 ​ 2016年八月份的一个下午,高考选完专业(计算机科学与技术)后,我在笔记本上敲下了第一个 “九九乘法表” 的 c 语言程序。当控制台输出熟悉的点阵数字的时候,我的编程生涯从就这一刻就开始了。 ...

2024-07-05 · 4 min · 1627 words · Luenci

golang 构造方法入参出参最佳实践

对上层保持简洁,对下层保持抽象 概念 接口 ​ 在 Golang 中,接口是一组方法签名。 当类型为接口中的所有方法提供定义时,它被称为实现接口。 它与 OOP(面向对象编程) 非常相似。 接口指定了类型应该具有的方法,类型决定了如何实现这些方法。 ...

2024-06-28 · 5 min · 2491 words · Luenci

浅谈Python垃圾回收机制

目前垃圾回收比较通用的解决办法有三种,引用计数,标记清除以及分代回收。 引用计数 引用计数也是一种最直观,最简单的垃圾收集技术。 在 Python 中,大多数对象的生命周期都是通过对象的引用计数来管理的。 其原理非常简单,我们为每个对象维护一个 ref 的字段用来记录对象被引用的次数,每当对象被创建或者被引用时将该对象的引用次数加一,当对象的引用被销毁时该对象的引用次数减一,当对象的引用次数减到零时说明程序中已经没有任何对象持有该对象的引用,换言之就是在以后的程序运行中不会再次使用到该对象了,那么其所占用的空间也就可以被释放了了。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import os import psutil def print_memory_info(name): """ 内存计算函数 """ pid = os.getpid() p = psutil.Process(pid) info = p.memory_full_info() MB = 1024 * 1024 memory = info.uss / MB print('%s used %d MB' % (name, memory)) def test(): print_memory_info('test start') length = 1000 * 1000 list1 = [i for i in range(length)] print_memory_info('test end') return list1 res = test() print_memory_info("main end") out: test start used 4 MB test end used 23 MB main end used 23 MB 由上述例子:如下情况会存在引用计数加一 ...

2024-07-07 · 10 min · 4744 words · Luenci

VictoriaMetricsn 原理入门

VictoriaMetricsn 原理入门 简介 VictoriaMetrics,是一个快速高效、经济并且可扩展的监控解决方案和时序数据库。 ​ 谈到VictoriaMetrics就必须要提到Prometheus,VictoriaMetrics是一个新兴的监控解决方案。它借助Prometheus强大的exporter生态、成熟的规范、服务发现等优点等,融入到Prometheus生态中。VictoriaMetrics官网很多兼容Prometheus参数解释都是直接跳转到Prometheus官网。 ​ VictoriaMetrics可以作为Prometheus的长期远程存储方案,当然 VictoriaMetrics 也可以完全取代 Prometheus,因为 VictoriaMetrics 基本支持 Prometheus配置文件、PromQL、各类API、数据格式等等。 VictoriaMetrics 优点 远程存储:可作为单一或多个Prometheus的远程存储 安装简单:单节点架构一条命令就可以部署完毕(集群方式稍微复杂一些,但也很好理解) 兼容性:PromQL兼容和增强的MetricsQL Grafana兼容:VM可替换Grafana的Prometheus数据源(经测试,线上数据源直接替换后100%兼容) 低内存:更低的内存占用,官方对比Prometheus,可以释放7倍左右内存空间(线上对比大概4倍) 高压缩比:提供存储数据高压缩,官方说可以比Prometheus减少7倍的存储空间(线上对比大概是4~5倍) 高性能:查询性能比Prometheus更快 支持水平扩容&HA:基于VM集群版实现 支持多租户:主要针对集群版 VictoriaMetrics 缺点 图形化做的不好,虽然有vmui,但功能很少 告警功能需要单独配置vmalert,而且vmalert只有api管理和查看,暂时没用图形界面 没有类似Prometheus的WAL日志,突然故障可能会丢失部分数据 ...

2024-06-24 · 6 min · 2797 words · Luenci

Redis五个使用场景

Redis五个使用场景 原文转自:https://www.jdon.com/69107.html 1.缓存 最常见的用例是利用 Redis 进行缓存。这有助于保护数据库层不超载。Redis 可为缓存数据提供快速查找时间,有助于提高应用程序性能。 ...

2024-07-11 · 1 min · 364 words · Luenci

what-happens-when(浏览器一次请求的历程)

what-happens-when(浏览器一次请求的历程) 原文来自:alex/what-happens-when: An attempt to answer the age old interview question “What happens when you type google.com into your browser and press enter?” — 亚历克斯/什么时候会发生什么:试图回答古老的面试问题“当你在浏览器中输入 google.com 并按回车键时会发生什么? (github.com) ...

2024-06-20 · 19 min · 9336 words · Luenci

Prometheus 概念入门

Prometheus 概念入门 数据模型 ​ Prometheus 所有采集的监控数据均以指标(metric)的形式保存在内置的时间序列数据库当中(TSDB):属于同一指标名称,同一标签集合的、有时间戳标记的数据流。除了存储的时间序列,Prometheus 还可以根据查询请求产生临时的、衍生的时间序列作为返回结果。 指标名称和标签 每一条时间序列由指标名称(Metrics Name)以及一组标签(键值对)唯一标识。其中指标的名称(metric name)可以反映被监控样本的含义(例如,http_requests_total — 表示当前系统接收到的 HTTP 请求总量),指标名称只能由 ASCII 字符、数字、下划线以及冒号组成,同时必须匹配正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*。 [info] 注意 冒号用来表示用户自定义的记录规则,不能在 exporter 中或监控对象直接暴露的指标中使用冒号来定义指标名称。 通过使用标签,Prometheus 开启了强大的多维数据模型:对于相同的指标名称,通过不同标签列表的集合,会形成特定的度量维度实例(例如:所有包含度量名称为 /api/tracks 的 http 请求,打上 method=POST 的标签,就会形成具体的 http 请求)。该查询语言在这些指标和标签列表的基础上进行过滤和聚合。改变任何度量指标上的任何标签值(包括添加或删除指标),都会创建新的时间序列。 标签的名称只能由 ASCII 字符、数字以及下划线组成并满足正则表达式 [a-zA-Z_][a-zA-Z0-9_]*。其中以 __ 作为前缀的标签,是系统保留的关键字,只能在系统内部使用。标签的值则可以包含任何 Unicode 编码的字符。 样本 在时间序列中的每一个点称为一个样本(sample),样本由以下三部分组成: 指标(metric):指标名称和描述当前样本特征的 labelsets; 时间戳(timestamp):一个精确到毫秒的时间戳; 样本值(value): 一个 folat64 的浮点型数据表示当前样本的值。 ...

2024-06-15 · 7 min · 3087 words · Luenci

夜莺监控入门

引子 监控系统必要性 ​ 作为运维者,第一个接触的基本上是监控平台,各种各样的监控,看各种各样的指标,好像没有监控就觉得不正常,那么为什么需要监控呢? 预防故障,例如当磁盘空间增长到一定的程度的时候,就会产生故障,这个时候监控系统的作用就是当达到一个阀值的时候,发出告警,然后进行处理。 预测变化趋势,例如我的分布式文件系统,每天数据增长1T空间,那么我总共有多少空间,剩余空间大小,是否要进行扩容等操作。 当故障发生的时候,能提供给我基本信息给与我排查的思路,例如redis不可读,是否能看到是哪个实例,能看到相关的日志信息,能测试是否刻读写,能查看哪个是master。 监控系统关键指标,例如对于web服务器来说,响应速度,来判断是否中间件有问题,是否数据库有问题,还是网络有问题;活跃的用户数,每天我的网站有多少用户访问;有多少新注册的用户。 简介 ​ 夜莺监控( Nightingale )是一款国产、开源云原生监控分析系统,采用 All-In-One 的设计,集数据采集、可视化、监控告警、数据分析于一体。于 2020 年 3 月 20 日,在 github 上发布 v1 版本,已累计迭代 60 多个版本。从 v5 版本开始与 Prometheus、VictoriaMetrics、Grafana、Telegraf、Datadog 等生态紧密协同集成,提供开箱即用的企业级监控分析和告警能力,已有众多企业选择将 Prometheus + AlertManager + Grafana 的组合方案升级为使用夜莺监控。夜莺监控,由滴滴开发和开源,并于 2022 年 5 月 11 日,捐赠予中国计算机学会开源发展委员会(CCF ODC),为 CCF ODC 成立后接受捐赠的第一个开源项目。夜莺监控的核心开发团队,也是Open-Falcon项目原核心研发人员。 产品介绍 开箱即用:支持 Docker、Helm Chart、云服务等多种部署方式;集数据采集、监控告警、可视化为一体;内置多种监控仪表盘、快捷视图、告警规则模板,导入即可快速使用;大幅降低云原生监控系统的建设成本、学习成本、使用成本 云原生:以交钥匙的方式快速构建企业级的云原生监控体系,支持 Categraf、Telegraf、Grafana-agent 等多种采集器,支持 Prometheus、VictoriaMetrics、M3DB、ElasticSearch 等多种数据库,兼容支持导入 Grafana 仪表盘,与云原生生态无缝集成 专业告警:可视化的告警配置和管理,支持丰富的告警规则,提供屏蔽规则、订阅规则的配置能力,支持告警多种送达渠道,支持告警自愈、告警事件管理等 灵活扩展、中心化管理:夜莺监控,可部署在 1 核 1G 的云主机,可在上百台机器集群化部署,可运行在 K8s 中;也可将时序库、告警引擎等组件下沉到各机房、各 Region,兼顾边缘部署和中心化统一管理,解决数据割裂,缺乏统一视图的难题 高性能、高可用:得益于夜莺的多数据源管理引擎,和夜莺引擎侧优秀的架构设计,借助于高性能时序库,可以满足数亿时间线的采集、存储、告警分析场景,节省大量成本;夜莺监控组件均可水平扩展,无单点,已在上千家企业部署落地,经受了严苛的生产实践检验 ​ Nightingale 可以接收各种采集器上报的监控数据,转存到时序库(可以支持Prometheus、M3DB、VictoriaMetrics、Thanos等),并提供告警规则、屏蔽规则、订阅规则的配置能力,提供监控数据的查看能力,提供告警自愈机制(告警触发之后自动回调某个webhook地址或者执行某个脚本),提供历史告警事件的存储管理、分组查看的能力。 ...

2024-06-10 · 10 min · 4640 words · Luenci

k8s 核心组件介绍

Etcd 储存核心实现 ​ Eted 集群是分布式KV存储集群,提供了可靠的强一致性服务发现。Etcd 集群存储 Kubernetes 系统的集群状态和元数据,其中包括所有 Kubemetes 资源对象信息、 资源对象状态、集群节点信息等。Kubernetes 将所有数据存储至 Etcd 集群前缀为 /registry 的目录下。 ...

2024-06-08 · 3 min · 1204 words · Luenci