# Golang能创建多长的数组? **Published by:** [hundredwz](https://paragraph.com/@hundredwz/) **Published on:** 2022-01-12 **URL:** https://paragraph.com/@hundredwz/golang ## Content Go最大能创建多大的数组?前几天,在写json反序列化时,服务端返回了一个非常大的数据,大约有七八万条。 当时在想,java的数据长度是固定的,最大是2^31-1,那么golang的呢? 这几天忙完了业务处理,准备测试下,看下最长的长度。 先说结论: go能够创建的数组长度大小,取决于类型和平台,在不同平台上,值是不同的测试首先创建一个数组arr:=make([]bool,math.MaxInt32) fmt.Println(len(arr)) 运行并获取结果根据结果判断,长度达到2^31-1没问题。 继续改动arr:=make([]bool,math.MaxUint32) fmt.Println(len(arr)) 发现依然正确的输出了结果这个值是2^32-1,已经超过了java的最大长度! 还能不能再大点?arr:=make([]bool,math.MaxInt64) fmt.Println(len(arr)) 这次报错了,提示panic: runtime error: makeslice: len out of range 但是!如果我把bool类型,替换成struct{}类型呢?arr:=make([]struct{},math.MaxInt64) fmt.Println(len(arr)) 成功创建再大点呢?arr:=make([]struct{},math.MaxUint64) fmt.Println(len(arr)) panic都没有,直接在编译期报错./main.go:9:11: len argument too large in make([]struct {}) 看来长度还是有点玄学。源码通过参考[1]可知,创建slice会在编译器进行处理,大致代码为# https://github.com/golang/go/blob/go1.15.6/src/cmd/compile/internal/gc/universe.go#L57 var builtinFuncs = [...]struct { name string op Op }{ ...... // 对应make的是OMAKE操作 {"make", OMAKE}, ...... } # https://github.com/golang/go/blob/go1.15.6/src/cmd/compile/internal/gc/typecheck.go#L1713 case OMAKE: ...... switch t.Etype { ...... case TSLICE: ...... // 参数校验 if !checkmake(t, "len", &l) || r != nil && !checkmake(t, "cap", &r) { n.Type = nil return n } ...... // 将具体操作方法定义为OMAKESLICE n.Op = OMAKESLICE # https://github.com/golang/go/blob/go1.15.6/src/cmd/compile/internal/gc/typecheck.go#L3710 func checkmake(t *types.Type, arg string, np **Node) bool { ...... switch consttype(n) { case CTINT, CTRUNE, CTFLT, CTCPLX: ...... // 如果当前值大于了maxint值,直接返回错误; // 跟我们创建MaxUint64的struct{} slice报错一致 if v.Cmp(maxintval[TINT]) > 0 { yyerror("%s argument too large in make(%v)", arg, t) return false } } 把操作符变成OMAKESLICE后,我们查看具体的创建流程,发现是通过walkexpr函数进行处理,然后通过mkcal1l调用的makeslice函数执行的创建# https://github.com/golang/go/blob/go1.15.6/src/cmd/compile/internal/gc/walk.go#L1317 case OMAKESLICE: ...... // n escapes; set up a call to makeslice. // When len and cap can fit into int, use makeslice instead of // makeslice64, which is faster and shorter on 32 bit platforms. len, cap := l, r // 调用函数为makeslice64 fnname := "makeslice64" argtype := types.Types[TINT64] // 如果slice的长度和cap都是int型,使用makeslice,提升在32bit机器上的性能 if (len.Type.IsKind(TIDEAL) || maxintval[len.Type.Etype].Cmp(maxintval[TUINT]) <= 0) && (cap.Type.IsKind(TIDEAL) || maxintval[cap.Type.Etype].Cmp(maxintval[TUINT]) <= 0) { fnname = "makeslice" argtype = types.Types[TINT] } m := nod(OSLICEHEADER, nil, nil) m.Type = t fn := syslook(fnname) m.Left = mkcall1(fn, types.Types[TUNSAFEPTR], init, typename(t.Elem()), conv(len, argtype), conv(cap, argtype)) ...... 之后需要查看makeslice函数具体的判断逻辑了# https://github.com/golang/go/blob/go1.15.6/src/runtime/slice.go#L83 func makeslice(et *_type, len, cap int) unsafe.Pointer { // 数组大小=数据类型大小*数组容量 // 如果数组大小超出了go支持的MaxUintptr值,就overflow了 mem, overflow := math.MulUintptr(et.size, uintptr(cap)) if overflow || mem > maxAlloc || len < 0 || len > cap { // 只是为了可读性,如果数据len都超出了,报长度不支持的错误 mem, overflow := math.MulUintptr(et.size, uintptr(len)) if overflow || mem > maxAlloc || len < 0 { panicmakeslicelen() } panicmakeslicecap() } return mallocgc(mem, et, true) } func makeslice64(et *_type, len64, cap64 int64) unsafe.Pointer { len := int(len64) if int64(len) != len64 { panicmakeslicelen() } cap := int(cap64) if int64(cap) != cap64 { panicmakeslicecap() } // makeslice64最底层还是会调用makeslice return makeslice(et, len, cap) } 可以看到,数组长度是否会报错,取决于三个条件申请的数组空间大小数值过大(如申请2^100000,go中的uint表示不了这么大的数)申请的数据空间过大,操作系统分配不了这么大数组长度为负那么系统的MaxUintptr是多少,可以分配的maxAlloc又是多少呢? 首先,我们可以看到# https://github.com/golang/go/blob/go1.15.6/src/runtime/internal/math/math.go#L9 const MaxUintptr = ^uintptr(0) MaxUintptr是对一个uintptr 0值全部取反,根据参考[3],可知在64位机器上,uintptr也是64位,即其最大值为0xffffffffffffffff 那么最大内存呢?# https://github.com/golang/go/blob/go1.15.6/src/runtime/malloc.go#L210 _64bit = 1 << (^uintptr(0) >> 63) / 2 // 32位系统为0,64位为1 ...... heapAddrBits = (_64bit*(1-sys.GoarchWasm)*(1-sys.GoosDarwin*sys.GoarchArm64))*48 + (1-_64bit+sys.GoarchWasm)*(32-(sys.GoarchMips+sys.GoarchMipsle)) + 33*sys.GoosDarwin*sys.GoarchArm64 maxAlloc = (1 << heapAddrBits) - (1-_64bit)*1 同时分析sys.GoarchWasm常量都是运行平台操作系统相关的,在笔者的mac 64机器上定义如下const GoarchWasm = 0 const GoosDarwin = 1 const GoarchArm64 = 0 const GoarchMips = 0 const GoarchMipsle = 0 我们可以计算得对地址位为heapAddrBits = (1 * ( 1 - 0 ) * ( 1 - 1 * 0 )) *48 + ( 1 - 1 + 0 ) * ( 32 - ( 0 + 0 )) + 33 * 1 * 0 = 48 可知一般分配的最大内存为( 1 << 48 ) - ( 1 -1 ) * 1 = 1<<48 为什么是48位,是由于硬件和操作系统的限制,在这就不赘述了。结论通过以上分析,我们可以得到结论,go能够创建的数组长度大小,取决于类型和平台,在不同平台上,值是不同的。 以我们通用的amd64平台来说,一般长度需要满足以下两个条件sizeof(data type) * len < ^uintptr(0) # 该值为2^64-1sizeof(data type) * len < maxAlloc # 该值为 2^48参考make原理分析make&new关键字uintptr定义 ## Publication Information - [hundredwz](https://paragraph.com/@hundredwz/): Publication homepage - [All Posts](https://paragraph.com/@hundredwz/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@hundredwz): Subscribe to updates