Golang字符串实际大小的基准测试 - 结果有误?
Golang字符串实际大小的基准测试 - 结果有误? 以下是我的基准测试:
func get() string {
return string([]byte{0x41, 0x42, 0x43})
}
var g string
func BenchmarkString(b *testing.B) {
for i := 0; i < b.N; i++ {
g = get()
}
fmt.Println("unsafe.Sizeof(g)", unsafe.Sizeof(g), b.N, g)
}
这个测试正确吗?以下是输出结果: unsafe.Sizeof(g) 16 100000000 ABC 100000000 13.7 ns/op 3 B/op 1 allocs/op
字符串的大小似乎是16字节,而基准测试显示每次操作只有3字节,这看起来不太合理。该如何解释?
我原本预期字符串的实际大小应该是: len(str) + unsafe.Sizeof(str) * 4
也就是说len(str)是开销,然后每个符文(len(str) = 符文数量)再占用4个字节,因为符文的大小与int32相同。如果我的理解有误,请指正。
更多关于Golang字符串实际大小的基准测试 - 结果有误?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
这是一篇关于内存分配器的好文章,它以易读的方式解释了许多内容:https://povilasv.me/go-memory-management/
更多关于Golang字符串实际大小的基准测试 - 结果有误?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
calmh:
三个字节(字符串数据并非"宽"字符,每个代码点占四个字节,它只是普通数据)。由于你将其传递给 fmt.Println,它会被分配到堆上,因此编译器无法保证它不会逃逸出当前函数的生命周期范围。
我已经注释掉了打印行,所以没有输出。但它仍然分配了 3 B/op。如果我将字符串中的符文数量增加到 4,它会分配 4 B/op,依此类推。
看起来这里的情况比表面更复杂。
字符串的Sizeof为16字节,因为字符串是一个包含指针(8字节)和长度(8字节)的结构体。这个结构体本身没有分配内存,因为它位于栈上。分配的是指向的数据,即三个字节(字符串数据不是"宽"字符,每个代码点四字节,它只是数据)。由于你将其传递给fmt.Println,它会在堆上分配内存,因为编译器无法保证它不会超出当前函数调用的生命周期。
你进行了大量的微基准测试并对结果提出疑问。你的探索是否有更宏观的意义?如果现在你应该得出一个结论,那就是对于这类小细节,一切都取决于具体情况。是否有内存分配取决于数据的使用方式等等。
您的基准测试结果实际上是正确的,但您对Go字符串内存结构的理解有误。让我解释一下:
Go字符串的内部结构
Go字符串在内存中是一个结构体,包含两个字段:
type stringStruct struct {
str unsafe.Pointer // 指向底层字节数组的指针
len int // 字符串长度
}
在64位系统上:
unsafe.Pointer占用 8 字节int占用 8 字节- 总计:16 字节
这就是为什么 unsafe.Sizeof(g) 返回 16 - 它测量的是字符串头结构的大小,而不是底层数据的大小。
基准测试结果分析
您的基准测试结果:
13.7 ns/op- 每次操作耗时3 B/op- 每次操作分配3字节(字符串"ABC"的实际内容)1 allocs/op- 每次操作1次内存分配
这是正确的,因为:
- 字符串"ABC"确实占用3个字节(每个ASCII字符1字节)
- 字符串头结构(16字节)在栈上分配,不计入堆分配
- 只有字符串内容(3字节)在堆上分配
验证示例
func TestStringMemory(t *testing.T) {
s := "ABC"
// 字符串头大小
fmt.Printf("String header size: %d bytes\n", unsafe.Sizeof(s))
// 字符串内容大小
fmt.Printf("String content size: %d bytes\n", len(s))
// 总内存占用(近似)
total := unsafe.Sizeof(s) + uintptr(len(s))
fmt.Printf("Total approximate memory: %d bytes\n", total)
}
输出:
String header size: 16 bytes
String content size: 3 bytes
Total approximate memory: 19 bytes
正确的理解
unsafe.Sizeof()返回类型本身的大小,不包含指针指向的数据- 基准测试的
B/op只计算堆分配,不包含栈分配 - 字符串内容以UTF-8编码存储,ASCII字符每个占用1字节
您的基准测试结果是准确的,反映了Go字符串内存管理的真实行为。


