fmtパッケージをインポートするとファイルサイズがどかんと増えてしまう理由

[Goアドベントカレンダー3](https://qiita.com/advent-calendar/2021/go)が穴が空いていたので小ネタを投稿してみます。

@masa_itoさんがGoConで、[Contextを完全に理解する](https://future-architect.github.io/articles/20211119a/)という発表をしました。その発表資料のP44に次のような記述があります。

![スクリーンショット 2021-12-06 1.54.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6105/bf47e197-d734-1381-1fa9-d6de91b8ed29.png)

会社のチャットで「fmtを使えば済みそうなところを、それを使わずに周りくどいことをしている」と発表資料を作っているときに@masa_itoさんが書いていて、僕が「fmtをインポートするだけでファイルサイズがかなり大きくなりますしね」と答えていたのですが、じゃあ何がでかくなるのかなってあたりまではその時は僕の方でも答えられなかったので簡単に調べてみました。

本当に"fmt"をインポートするだけのコードを書いてそれぞれビルドしました。macOSです。

```go:main-with-fmt.go
package main

import (
	_ "fmt"
)

func main() {
}
```

```go:main-without-fmt.go
package main

func main() {
}
```

macOSなので、バイナリに含まれるシンボルテーブルはnmコマンドで調べられます。こんな感じのテキストが表示されます。

```bash
$ npm nm main-with-fmt
:
000000010005b8e0 t _notok
                 U _open
0000000100070c10 t _os.(*SyscallError).Error
0000000100070ff0 t _os.(*file).close
00000001000f3260 s _os..inittask
0000000100103f40 b _os.Args
0000000100103dc0 b _os.ErrClosed
0000000100103dd0 b _os.ErrDeadlineExceeded
0000000100103de0 b _os.ErrExist
0000000100103df0 b _os.ErrInvalid
```

アドレスはまあバイナリによって違いそうなので、シンボル名だけ抜き出して差分を取るようにしてみました。訳あってPython脳なのでPythonでやりました。すまん。

```py:dump.py
import subprocess
from typing import List, Set

execs = ['main-with-fmt', 'main-without-fmt']

results: List[Set[str]] = []

for exec in execs:
    p = subprocess.run(['nm', exec], capture_output=True)
    result: Set[str] = set()
    for line in p.stdout.decode('utf-8').split("\n"):
        symbol = line[len('00000001000118d0 t '):] # 長さ数えるの面倒だったのでテキストそのまま突っ込んだ
        result.add(symbol)
    results.append(result)

print("増えた")
for symbol in sorted(results[0].difference(results[1])):
    print(f"* {symbol}")

print("減った")
for symbol in sorted(results[1].difference(results[0])):
    print(f"* {symbol}")
```

実行結果はエントリーの末尾に貼り付けておきます。注目すべきは、fmtパッケージの要素としてこれだけの要素しか増えてない点です。

```
* _fmt..inittask
* _fmt.boolError
* _fmt.complexError
* _fmt.init
```

しかし、パッケージとしては以下のような依存が発生してしまっています。internalは抜きます。

* errors
* go
* io
* os
* path
* reflect
* sort
* strconv
* sync
* syscall
* unicode
* time
* type

Goはリンク時に不要な関数は削除してバイナリをなるべく小さくしようとするのですが、Goの言語仕様&エスケープ解析の欠点としてはinit()で参照（パッケージグローバル変数の宣言もコンパイル時に生成されるinit()にまとめられるので、グローバル変数宣言も）されたものはすべて、eliminationが働かずにリンクされちゃうんですよね。

`%v`でどんな型がきてもパースしてやるぜ、という機能があるのでおそらくそこでリフレクションをimportしていて、そのimportの中にinit()があれば、そこで使っている要素がシンボルとして残り・・・みたいな連鎖でしょうね。osは出力先のstdoutとかからの繋がりでしょうかね。strconvやtimeも数値などの形式変換ですかね。

まあ、もっとも大きいのはunicodeのテーブルでしょうね。文字列を出力する、バイトコードと文字列の相互変換には文字コードごとの文字の種別とか諸々知らなければならないのですよね。変な文字コードの場合はエラーにしたりとかが必要なので、unicode情報をアプリに持たないといけないのです。このあたりはGoCon 2021 Autumnで、ARIGATOBANKの大和屋さんの発表も参考にしてもらえるとよく理解できると思います。

https://gocon.jp/2021autumn/sessions/go-unicode/

パッケージグローバルな変数を初期化するためにinit()をGoコンパイラが自動で作るときに、「この関数はこの要素に依存している」とかまでフラグをつけて、パッケージグローバルな関数であっても利用していなかったら何もしない、ぐらいあったらよかったのになぁ、と思ったり思わなかったり。

本当はnmコマンドでサイズ情報まで取ってパッケージごとの寄与分を見える化しようと思ったけど`--print-size`オプションをつけても表示されなかったのでここまで。

# (参考)増えたシンボル

```
増えた
* _closedir
* _errors.(*errorString).Error
* _errors..inittask
* _errors.errorType
* _errors.init
* _execve
* _fmt..inittask
* _fmt.boolError
* _fmt.complexError
* _fmt.init
* _fstat
* _getcwd
* _go.itab.*errors.errorString,error
* _go.itab.*internal/poll.DeadlineExceededError,error
* _go.itab.*internal/reflectlite.rtype,internal/reflectlite.Type
* _go.itab.*io/fs.PathError,error
* _go.itab.*os.SyscallError,error
* _go.itab.*os.fileStat,io/fs.FileInfo
* _go.itab.*reflect.rtype,reflect.Type
* _go.itab.internal/poll.errNetClosing,error
* _go.itab.syscall.Errno,error
* _go.itab.time.fileSizeError,error
* _internal/fmtsort..inittask
* _internal/itoa.Itoa
* _internal/oserror..inittask
* _internal/oserror.ErrClosed
* _internal/oserror.ErrExist
* _internal/oserror.ErrInvalid
* _internal/oserror.ErrNotExist
* _internal/oserror.ErrPermission
* _internal/oserror.init
* _internal/poll.(*DeadlineExceededError).Error
* _internal/poll.(*FD).Close
* _internal/poll.(*FD).Init
* _internal/poll.(*FD).decref
* _internal/poll.(*FD).destroy
* _internal/poll.(*errNetClosing).Error
* _internal/poll.(*fdMutex).increfAndClose
* _internal/poll.(*pollDesc).init
* _internal/poll..inittask
* _internal/poll.CloseFunc
* _internal/poll.ErrDeadlineExceeded
* _internal/poll.ErrFileClosing
* _internal/poll.ErrNoDeadline
* _internal/poll.ErrNotPollable
* _internal/poll.errEAGAIN
* _internal/poll.errEINVAL
* _internal/poll.errENOENT
* _internal/poll.errNetClosing.Error
* _internal/poll.init
* _internal/poll.runtime_Semacquire
* _internal/poll.runtime_Semrelease
* _internal/poll.runtime_pollClose
* _internal/poll.runtime_pollOpen
* _internal/poll.runtime_pollServerInit
* _internal/poll.runtime_pollUnblock
* _internal/poll.serverInit
* _internal/reflectlite.(*Kind).String
* _internal/reflectlite.(*rtype).Elem
* _internal/reflectlite.(*rtype).String
* _internal/reflectlite..inittask
* _internal/reflectlite.Kind.String
* _internal/reflectlite.kindNames
* _internal/reflectlite.name.name
* _internal/reflectlite.resolveNameOff
* _internal/syscall/execenv..inittask
* _internal/syscall/unix..inittask
* _internal/syscall/unix.IsNonblock
* _internal/testlog..inittask
* _internal/testlog.Getenv
* _internal/testlog.logger
* _io..inittask
* _io.EOF
* _io.ErrClosedPipe
* _io.ErrNoProgress
* _io.ErrShortBuffer
* _io.ErrShortWrite
* _io.ErrUnexpectedEOF
* _io.errInvalidWrite
* _io.errOffset
* _io.errWhence
* _io.init
* _io/fs.(*FileMode).String
* _io/fs.(*PathError).Error
* _io/fs..inittask
* _io/fs.ErrClosed
* _io/fs.ErrExist
* _io/fs.ErrInvalid
* _io/fs.ErrNotExist
* _io/fs.ErrPermission
* _io/fs.FileMode.String
* _io/fs.SkipDir
* _io/fs.init
* _lseek
* _os.(*SyscallError).Error
* _os.(*file).close
* _os..inittask
* _os.Args
* _os.ErrClosed
* _os.ErrDeadlineExceeded
* _os.ErrExist
* _os.ErrInvalid
* _os.ErrNoDeadline
* _os.ErrNotExist
* _os.ErrPermission
* _os.ErrProcessDone
* _os.Getenv
* _os.Getwd
* _os.NewFile
* _os.Stderr
* _os.Stdin
* _os.Stdout
* _os.errPatternHasSeparator
* _os.errWriteAtInAppendMode
* _os.fillFileStatFromSys
* _os.init
* _os.init.0
* _os.initCwd
* _os.initCwdErr
* _os.newFile
* _os.runtime_args
* _os.statNolog
* _path..inittask
* _path.ErrBadPattern
* _path.init
* _reflect.(*ChanDir).String
* _reflect.(*Kind).String
* _reflect.(*Value).String
* _reflect.(*ValueError).Error
* _reflect.(*rtype).String
* _reflect.(*rtype).exportedMethods
* _reflect.(*rtype).uncommon
* _reflect..inittask
* _reflect.ChanDir.String
* _reflect.Kind.String
* _reflect.Value.String
* _reflect.Value.Type
* _reflect.init
* _reflect.kindNames
* _reflect.name.name
* _reflect.resolveNameOff
* _reflect.resolveTypeOff
* _reflect.uint8Type
* _runtime.(*pollCache).alloc
* _runtime.(*pollCache).free
* _runtime.SetFinalizer
* _runtime.SetFinalizer.func1
* _runtime.SetFinalizer.func2
* _runtime.addfinalizer
* _runtime.assertE2I
* _runtime.concatstring3
* _runtime.concatstring5
* _runtime.convT64
* _runtime.createfing
* _runtime.decoderune
* _runtime.defaultGOROOT
* _runtime.defaultGOROOT.str
* _runtime.evacuate_faststr
* _runtime.fingCreate
* _runtime.growWork_faststr
* _runtime.intArgRegs
* _runtime.makemap_small
* _runtime.mapaccess2_faststr
* _runtime.mapassign_faststr
* _runtime.nanotime
* _runtime.netpollopen
* _runtime.panicdottypeE
* _runtime.panicdottypeI
* _runtime.pollcache
* _runtime.rawbyteslice
* _runtime.removefinalizer
* _runtime.removespecial
* _runtime.runfinq
* _runtime.strhash
* _runtime.strhashFallback
* _runtime.stringtoslicebyte
* _runtime.syscall
* _runtime.syscall6X
* _runtime.syscallX
* _runtime/internal/atomic.Store
* _runtime/internal/atomic.Store.args_stackmap
* _sort..inittask
* _stat
* _strconv..inittask
* _strconv.ErrRange
* _strconv.ErrSyntax
* _strconv.FormatInt
* _strconv.formatBits
* _strconv.init
* _sync.(*Mutex).Unlock
* _sync.(*Mutex).lockSlow
* _sync.(*Mutex).unlockSlow
* _sync.(*Once).doSlow
* _sync.(*RWMutex).RUnlock
* _sync.(*RWMutex).rUnlockSlow
* _sync..inittask
* _sync.allPools
* _sync.expunged
* _sync.init
* _sync.init.0
* _sync.init.1
* _sync.oldPools
* _sync.poolCleanup
* _sync.runtime_SemacquireMutex
* _sync.runtime_Semrelease
* _sync.runtime_canSpin
* _sync.runtime_doSpin
* _sync.runtime_nanotime
* _sync.runtime_notifyListCheck
* _sync.runtime_registerPoolCleanup
* _sync.throw
* _sync/atomic.StoreUint32
* _sync/atomic.StoreUint32.args_stackmap
* _syscall.(*Errno).Error
* _syscall..inittask
* _syscall.Close
* _syscall.Errno.Error
* _syscall.Fstat
* _syscall.Getenv
* _syscall.Getwd
* _syscall.Open
* _syscall.Seek
* _syscall.SetNonblock
* _syscall.Stat
* _syscall.Stderr
* _syscall.Stdin
* _syscall.Stdout
* _syscall._zero
* _syscall.closedir
* _syscall.copyenv
* _syscall.env
* _syscall.envLock
* _syscall.envOnce
* _syscall.envs
* _syscall.errEAGAIN
* _syscall.errEINVAL
* _syscall.errENOENT
* _syscall.errors
* _syscall.execve
* _syscall.execveDarwin
* _syscall.fcntl
* _syscall.getcwd
* _syscall.init
* _syscall.init.0
* _syscall.libc_close_trampoline
* _syscall.libc_close_trampoline.args_stackmap
* _syscall.libc_closedir_trampoline
* _syscall.libc_closedir_trampoline.args_stackmap
* _syscall.libc_execve_trampoline
* _syscall.libc_execve_trampoline.args_stackmap
* _syscall.libc_fcntl_trampoline
* _syscall.libc_fcntl_trampoline.args_stackmap
* _syscall.libc_fstat_trampoline
* _syscall.libc_fstat_trampoline.args_stackmap
* _syscall.libc_getcwd_trampoline
* _syscall.libc_getcwd_trampoline.args_stackmap
* _syscall.libc_lseek_trampoline
* _syscall.libc_lseek_trampoline.args_stackmap
* _syscall.libc_mmap_trampoline
* _syscall.libc_mmap_trampoline.args_stackmap
* _syscall.libc_munmap_trampoline
* _syscall.libc_munmap_trampoline.args_stackmap
* _syscall.libc_open_trampoline
* _syscall.libc_open_trampoline.args_stackmap
* _syscall.libc_read_trampoline
* _syscall.libc_read_trampoline.args_stackmap
* _syscall.libc_stat_trampoline
* _syscall.libc_stat_trampoline.args_stackmap
* _syscall.minRoutingSockaddrLen
* _syscall.mmap
* _syscall.munmap
* _syscall.rawSyscall
* _syscall.read
* _syscall.runtime_envs
* _syscall.syscall
* _syscall.syscall6X
* _syscall.syscallX
* _time.(*Location).String
* _time.(*Location).firstZoneUsed
* _time.(*Location).get
* _time.(*Location).lookup
* _time.(*Location).lookupFirstZone
* _time.(*Time).String
* _time.(*dataIO).big4
* _time.(*dataIO).big8
* _time.(*fileSizeError).Error
* _time..inittask
* _time.LoadLocationFromTZData
* _time.Local
* _time.Time.AppendFormat
* _time.Time.Format
* _time.Time.String
* _time.Time.locabs
* _time.absDate
* _time.appendInt
* _time.atoiError
* _time.badData
* _time.closefd
* _time.daysBefore
* _time.errBad
* _time.errLeadingInt
* _time.errLocation
* _time.fileSizeError.Error
* _time.findZone
* _time.formatNano
* _time.init
* _time.initLocal
* _time.loadFromEmbeddedTZData
* _time.loadLocation
* _time.loadTzinfo
* _time.loadTzinfoFromDirOrZip
* _time.loadTzinfoFromTzdata
* _time.loadTzinfoFromZip
* _time.localLoc
* _time.localOnce
* _time.longDayNames
* _time.longMonthNames
* _time.nextStdChunk
* _time.preadn
* _time.readFile
* _time.startNano
* _time.std0x
* _time.tzruleTime
* _time.tzset
* _time.tzsetName
* _time.tzsetNum
* _time.tzsetOffset
* _time.tzsetRule
* _time.unitMap
* _time.utcLoc
* _time.zoneSources
* _type..eq.internal/poll.FD
* _type..eq.internal/reflectlite.uncommonType
* _type..eq.io/fs.PathError
* _type..eq.os.SyscallError
* _type..eq.os.file
* _type..eq.os.fileStat
* _type..eq.reflect.Method
* _type..eq.reflect.ValueError
* _type..eq.reflect.uncommonType
* _type..eq.time.zone
* _type..eq.time.zoneTrans
* _unicode..inittask
* _unicode.ASCII_Hex_Digit
* _unicode.Adlam
* _unicode.Ahom
* _unicode.Anatolian_Hieroglyphs
* _unicode.Arabic
* _unicode.Armenian
* _unicode.Avestan
* _unicode.Balinese
* _unicode.Bamum
* _unicode.Bassa_Vah
* _unicode.Batak
* _unicode.Bengali
* _unicode.Bhaiksuki
* _unicode.Bidi_Control
* _unicode.Bopomofo
* _unicode.Brahmi
* _unicode.Braille
* _unicode.Buginese
* _unicode.Buhid
* _unicode.C
* _unicode.Canadian_Aboriginal
* _unicode.Carian
* _unicode.Categories
* _unicode.Caucasian_Albanian
* _unicode.Cc
* _unicode.Cf
* _unicode.Chakma
* _unicode.Cham
* _unicode.Cherokee
* _unicode.Chorasmian
* _unicode.Co
* _unicode.Common
* _unicode.Coptic
* _unicode.Cs
* _unicode.Cuneiform
* _unicode.Cypriot
* _unicode.Cyrillic
* _unicode.Dash
* _unicode.Deprecated
* _unicode.Deseret
* _unicode.Devanagari
* _unicode.Diacritic
* _unicode.Dives_Akuru
* _unicode.Dogra
* _unicode.Duployan
* _unicode.Egyptian_Hieroglyphs
* _unicode.Elbasan
* _unicode.Elymaic
* _unicode.Ethiopic
* _unicode.Extender
* _unicode.FoldCategory
* _unicode.FoldScript
* _unicode.Georgian
* _unicode.Glagolitic
* _unicode.Gothic
* _unicode.Grantha
* _unicode.Greek
* _unicode.Gujarati
* _unicode.Gunjala_Gondi
* _unicode.Gurmukhi
* _unicode.Han
* _unicode.Hangul
* _unicode.Hanifi_Rohingya
* _unicode.Hanunoo
* _unicode.Hatran
* _unicode.Hebrew
* _unicode.Hex_Digit
* _unicode.Hiragana
* _unicode.Hyphen
* _unicode.IDS_Binary_Operator
* _unicode.IDS_Trinary_Operator
* _unicode.Ideographic
* _unicode.Imperial_Aramaic
* _unicode.Inherited
* _unicode.Inscriptional_Pahlavi
* _unicode.Inscriptional_Parthian
* _unicode.Javanese
* _unicode.Join_Control
* _unicode.Kaithi
* _unicode.Kannada
* _unicode.Katakana
* _unicode.Kayah_Li
* _unicode.Kharoshthi
* _unicode.Khitan_Small_Script
* _unicode.Khmer
* _unicode.Khojki
* _unicode.Khudawadi
* _unicode.L
* _unicode.Lao
* _unicode.Latin
* _unicode.Lepcha
* _unicode.Limbu
* _unicode.Linear_A
* _unicode.Linear_B
* _unicode.Lisu
* _unicode.Ll
* _unicode.Lm
* _unicode.Lo
* _unicode.Logical_Order_Exception
* _unicode.Lt
* _unicode.Lu
* _unicode.Lycian
* _unicode.Lydian
* _unicode.M
* _unicode.Mahajani
* _unicode.Makasar
* _unicode.Malayalam
* _unicode.Mandaic
* _unicode.Manichaean
* _unicode.Marchen
* _unicode.Masaram_Gondi
* _unicode.Mc
* _unicode.Me
* _unicode.Medefaidrin
* _unicode.Meetei_Mayek
* _unicode.Mende_Kikakui
* _unicode.Meroitic_Cursive
* _unicode.Meroitic_Hieroglyphs
* _unicode.Miao
* _unicode.Mn
* _unicode.Modi
* _unicode.Mongolian
* _unicode.Mro
* _unicode.Multani
* _unicode.Myanmar
* _unicode.N
* _unicode.Nabataean
* _unicode.Nandinagari
* _unicode.Nd
* _unicode.New_Tai_Lue
* _unicode.Newa
* _unicode.Nko
* _unicode.Nl
* _unicode.No
* _unicode.Noncharacter_Code_Point
* _unicode.Nushu
* _unicode.Nyiakeng_Puachue_Hmong
* _unicode.Ogham
* _unicode.Ol_Chiki
* _unicode.Old_Hungarian
* _unicode.Old_Italic
* _unicode.Old_North_Arabian
* _unicode.Old_Permic
* _unicode.Old_Persian
* _unicode.Old_Sogdian
* _unicode.Old_South_Arabian
* _unicode.Old_Turkic
* _unicode.Oriya
* _unicode.Osage
* _unicode.Osmanya
* _unicode.Other_Alphabetic
* _unicode.Other_Default_Ignorable_Code_Point
* _unicode.Other_Grapheme_Extend
* _unicode.Other_ID_Continue
* _unicode.Other_ID_Start
* _unicode.Other_Lowercase
* _unicode.Other_Math
* _unicode.Other_Uppercase
* _unicode.P
* _unicode.Pahawh_Hmong
* _unicode.Palmyrene
* _unicode.Pattern_Syntax
* _unicode.Pattern_White_Space
* _unicode.Pau_Cin_Hau
* _unicode.Pc
* _unicode.Pd
* _unicode.Pe
* _unicode.Pf
* _unicode.Phags_Pa
* _unicode.Phoenician
* _unicode.Pi
* _unicode.Po
* _unicode.Prepended_Concatenation_Mark
* _unicode.Properties
* _unicode.Ps
* _unicode.Psalter_Pahlavi
* _unicode.Quotation_Mark
* _unicode.Radical
* _unicode.Regional_Indicator
* _unicode.Rejang
* _unicode.Runic
* _unicode.S
* _unicode.Samaritan
* _unicode.Saurashtra
* _unicode.Sc
* _unicode.Scripts
* _unicode.Sentence_Terminal
* _unicode.Sharada
* _unicode.Shavian
* _unicode.Siddham
* _unicode.SignWriting
* _unicode.Sinhala
* _unicode.Sk
* _unicode.Sm
* _unicode.So
* _unicode.Soft_Dotted
* _unicode.Sogdian
* _unicode.Sora_Sompeng
* _unicode.Soyombo
* _unicode.Sundanese
* _unicode.Syloti_Nagri
* _unicode.Syriac
* _unicode.Tagalog
* _unicode.Tagbanwa
* _unicode.Tai_Le
* _unicode.Tai_Tham
* _unicode.Tai_Viet
* _unicode.Takri
* _unicode.Tamil
* _unicode.Tangut
* _unicode.Telugu
* _unicode.Terminal_Punctuation
* _unicode.Thaana
* _unicode.Thai
* _unicode.Tibetan
* _unicode.Tifinagh
* _unicode.Tirhuta
* _unicode.Ugaritic
* _unicode.Unified_Ideograph
* _unicode.Vai
* _unicode.Variation_Selector
* _unicode.Wancho
* _unicode.Warang_Citi
* _unicode.White_Space
* _unicode.Yezidi
* _unicode.Yi
* _unicode.Z
* _unicode.Zanabazar_Square
* _unicode.Zl
* _unicode.Zp
* _unicode.Zs
* _unicode.foldCommon
* _unicode.foldGreek
* _unicode.foldInherited
* _unicode.foldL
* _unicode.foldLl
* _unicode.foldLt
* _unicode.foldLu
* _unicode.foldM
* _unicode.foldMn
* _unicode.init
減った
```