0
package main

type TreeCell struct {
    Tabs func() *string
}

func Cell() *string {
    s:= ""
    return &s
}

func Table(Line *[]TreeCell) {
    if Line != nil {
        Num["rtt"] = Line
    }
}

var (
    Num map[string]*[]TreeCell 
)

func main() {

    Table(&[]TreeCell{
        TreeCell{Tabs: Cell},
        TreeCell{Tabs: Cell},
        ...repeat 15000 times
        TreeCell{Tabs: Cell},
    })
}

go build -a -v -gcflags "-N -l" -ldflags "-s -w"

Size of executable file 1,9Mb

__text              1459891   16781312
__rodata             158107   18241216
Total               1951521

if I change the func() *string to the interface{}

type TreeCell struct {
    Tabs interface{}
}

then size of executable file 32Mb

__text               1864389   16781312
__rodata            30375699   18645728
Total               32698219

Why?

Go version 1.9.2

  • Please check this: https://stackoverflow.com/questions/22770114/any-difference-in-using-an-empty-interface-or-an-empty-struct-as-a-maps-value-i – Sergii Bishyr Dec 25 '17 at 13:49
  • __text (code) increased by only 404Kb. 404kb / ~15000 =~ 28 byte pro TreeCell{Tabs: Cell}. I believe that abnormally increased __rodata (data) – Andrey Voronko Dec 25 '17 at 14:15
  • I think it would be better if you change the header of your question to underline that the question is about size of result binary file. – ceth Dec 25 '17 at 14:57

1 Answers1

0

The size of an interface{} variable is 8 or 16 bytes (depending on the architecture being 32 or 64 bit), and the size of a function variable is either 4 or 8 bytes. So there is only a 2 multiplication which would not explain the big difference in the output binary (15.000 * 8 bytes is only 120 KB).

What you experience is the result of different inlining compiler optimization. The function Cell() is very simple and is eligible for inlining.

When inlining is disabled

If we include the -gcflags '-N -l' params like in your example (these flags tell the compiler to disable inlining), then references to Cell are not inlined, so using func() *string will only use a 4-byte function pointer.

Using interface{} however will result in inlining Cell values. Interface values hold a copy, and the function call is not inlined, but when the function value is used when implicitly wrapped in interface{} values, it is inlined (duplicated). In each time, that is 15.000 times! So basically the Cell() function body is included 15.000 times. Now that's big, that is what makes the resulting binary 30 MB.

When inlining is enabled

If we exclude the -gcflags '-N -l' params, it's actually the opposite: the compiled binary is about 2 MB when using interface{}, and it's about 30 MB when using func () *string.

This time when using the Cell function value in the composite literal for initializing the TreeCell.Tabs field, the compiler will inline the Cell() function in all 15.000 times! When using interface{}, the function bodies will not be inlined however. Not sure why, a possible explanation is that it is also inlined in case of interface{}, and since interface values are immutable, the same value is used 15.000 times.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thank you for answer. Why does the size __rodata increase and not __text? – Andrey Voronko Dec 25 '17 at 16:08
  • @AndreyVoronko Why would `__text` increase and not `__rodata`? Function body is duplicated 15.000 times, that is not text but code. – icza Dec 25 '17 at 16:10
  • `__text` is code. `__rodata` is readonly data (constant, strings e.t.c ) Flags `-l` (disable inline) does not affect the result. Only `-N` – Andrey Voronko Dec 25 '17 at 16:15
  • @AndreyVoronko Then you answered it: `__text` is code, and that is being duplicated by inlining. – icza Dec 25 '17 at 16:27
  • I think that this is not inline. If this would be inline, then increasing the inline function's code would increase the size of the executable file. I changed the code `func Cell() *string { s:= "01234567890102345678901234567890123456789012345678900123456789010234567890123456789012345678901234567890" return &s }` but the size remained the same – Andrey Voronko Dec 26 '17 at 09:17
  • @AndreyVoronko Your change does not result in bigger binary because that string literal (constant) is only stored once in the binary. – icza Dec 26 '17 at 09:33
  • I tried the same func Cell() *string { s:= "" fmt.Println("sdsdsdsd") return &s } – Andrey Voronko Dec 26 '17 at 10:11
  • Ok. If it is inline. 30MB / 15000 = 2KB I think it's a lot for func Cell() *string { s:= "" return &s } – Andrey Voronko Dec 26 '17 at 10:22