Heap allocation

Many operations in Go rely on heap allocation. TinyGo will try to optimize them away using escape analysis, but that is not always possible in practice.

These operations currently do heap allocations:

  • Taking the pointer of a local variable. This will result in a heap allocation, unless the compiler can see the resulting pointer never escapes. This causes a heap allocation:
  1. var global *int
  2. func foo() {
  3. i := 3
  4. global = &i
  5. }

This does not cause a heap allocation:

  1. func foo() {
  2. i := 3
  3. bar(&i)
  4. }
  5. func bar(i *int) {
  6. println(*i)
  7. }
  • Converting between string and []byte. In general, this causes a heap allocation because one is constant while the other is not: for example, a []byte is not allowed to write to the underlying buffer of a string. However, there is an optimization that avoids a heap allocation when converting a string to a []byte when the compiler can see the slice is never written to. For example, this WriteString function does not cause a heap allocation:
  1. func WriteString(s string) {
  2. Write([]byte(s))
  3. }
  4. func Write(buf []byte) {
  5. for _, c := range buf {
  6. WriteByte(c)
  7. }
  8. }
  • Converting a byte or rune into a string. This operation is actually a conversion from a Unicode code point into a single-character string so is similar to the previous point.

  • Concatenating strings, unless one of them is zero length.

  • Creating an interface with a value larger than a pointer. Interfaces in Go are not a zero-cost abstraction and should be used carefully on microcontrollers.

  • Closures where the collection of shared variables between the closure and the main function is larger than a pointer.

  • Creating and modifying maps.

  • Starting goroutines.