[] what is the default cap for byte (")?

s := []byte("")
fmt.Println(cap(s))  //32

but

s := []byte("")
fmt.Println(cap(s))  //0
fmt.Println(s)

Is the compiler does some optimizations?

Mar.16,2021

this phenomenon is indeed caused by compiler optimization, and we can find some evidence from the source code.

137995 0000000000483200 <main.main>:

138009  48323b:      0f 11 44 24 08          movups %xmm0,0x8(%rsp)
138010  483240:      e8 bb a4 fb ff          callq  43d700 <runtime.stringtoslicebyte>
138011  483245:      48 8b 44 24 20          mov    0x20(%rsp),%rax
138012  48324a:      48 8b 4c 24 28          mov    0x28(%rsp),%rcx

We disassemble the program. As shown above, for statements like s: = [] byte (""), the compiler generates a stringtoslicebyte function for us to convert from string to slice. This function is defined in:

146 func stringtoslicebyte(buf *tmpBuf, s string) []byte {
147        var b []byte
148        if buf != nil && len(s) <= len(buf) {
149                *buf = tmpBuf{} 
150                b = buf[:len(s)]
151        } else {
152                b = rawbyteslice(len(s))
153        }
154        copy(b, s)
155        return b
156 }
"runtime/string.go" 443 lines --22%--                

tmpBuf is defined as an array of length 32.

9 // The constant is known to the compiler.
10 // There is no fundamental theory behind this number.
11 const tmpStringBufSize = 32
12
13 type tmpBuf [tmpStringBufSize]byte
14

when stringtoslicebyte takes the first branch, it allocates memory from the stack. If allocated from the stack, it allocates an array of length 32, which is a dead value agreed with the compiler.

when stringtoslicebyte takes the second branch, memory is allocated from the heap, and if allocated from the heap, it is allocated based on the actual length of the string.

255 // rawbyteslice allocates a new byte slice. The byte slice is not zeroed.
256 func rawbyteslice(size int) (b []byte) {
257        cap := roundupsize(uintptr(size))
258        p := mallocgc(cap, nil, false)
259        if cap != uintptr(size) {
260                memclrNoHeapPointers(add(p, uintptr(size)), cap-uintptr(size))
261        }
262
263        *(*slice)(unsafe.Pointer(&b)) = slice{p, size, int(cap)}
264        return
265 }
"runtime/string.go" 443 lines --47%--               

We then search the source code of the compiler to find the generation logic of stringtoslicebyte.

1635        case OSTRARRAYBYTE:
1636                a := nodnil()
1637
1638                if n.Esc == EscNone {
1639                        // Create temporary buffer for slice on stack.
1640                        t := types.NewArray(types.Types[TUINT8], tmpstringbufsize)
1641
1642                        a = nod(OADDR, temp(t), nil)
1643                }
1644
1645                n = mkcall("stringtoslicebyte", n.Type, init, a, conv(n.Left, types.Types[TSTRING]))
"cmd/compile/internal/gc/walk.go" 3928 lines --40%--                                                                   

according to the comments, the condition for the compiler to judge whether to allocate on the stack is whether the object will escape-
the compiler will determine whether an object will be referenced outside the current function, and if not, it can be optimized by allocating the object on the current stack without GC processing.
this process is part of compiler escape analysis (optimization). The related source code of escape analysis is
"cmd/compile/internal/gc/esc.go"

.

the result of compiler escape analysis can be obtained by using the following command

results of escape analysis without fmt.Println (s):

$ go tool compile -m test.go
test.go:7:16: cap(s) escapes to heap
test.go:7:24: len(s) escapes to heap
test.go:6:12: main ([]byte)("") does not escape
test.go:7:12: main ... argument does not escape

results of escape analysis with fmt.Println (s):

$ go tool compile -m test.go
test.go:7:16: cap(s) escapes to heap
test.go:7:24: len(s) escapes to heap
test.go:8:12: s escapes to heap
test.go:6:12: ([]byte)("") escapes to heap
test.go:7:12: main ... argument does not escape
test.go:8:12: main ... argument does not escape
The above basically explains what we have seen.


this is an interesting question. As a result, the compiler must have optimized the temporary array variables allocated on the stack (the temporarily allocated capacity of a fixed size), and once the array variables are manipulated, the capacity size of the array variables will be updated according to actual usage.

in addition, if the array is defined as a package internal variable or a global variable, the result of this cap will also be 0, because it will be allocated on the heap.

I am only guessing based on the test results, and the specific internal implementation details may need Daniel to popularize science.

Menu