код - Пробежимся по коду - Пробежимся по коду - Пробежимся по коду - Пробежимся по коду - Пробежимся по коду - Пробежимся по коду - Пробежимся по коду План доклада 11
код - Пробежимся по коду - Пробежимся по коду - Пробежимся по коду - Пробежимся по коду - Пробежимся по коду - Пробежимся по коду - Пробежимся по коду - Интересное из stdlib - Exit Go 1.16.3, OS Darwin (macOS Big Sur), YMMV План доклада 12
есть - Но действительно проблематит не всех - размер Docker image чаще выступает проблемой - если вы на Ubuntu/Debian строитесь - + можно через UPX пожать бинарь - Но что там минимально-нужного?
есть - Но действительно проблематит не всех - размер Docker image чаще выступает проблемой - если вы на Ubuntu/Debian строитесь - + можно через UPX пожать бинарь - Но что там минимально-нужного? - Про это и поговорим
Go code alongside assembly") - Еще есть флаг symregexp flag.String("s", "", "only dump symbols matching this regexp") ❯ go tool objdump -S -s runtime.close main.exec go tool objdump 44
main_main() // ... // The main goroutine. func main() { g := getg() // ... fn := main_main // make an indirect call, // as the linker doesn't know the address // of the main package when laying down the runtime fn() // ...
архитектура (amd64, arm64, mips, …) - *.s - Assembler - 👀 - Значит пойдём разбираться - Посмотрим на asm_amd64.s - всего лишь 2к строк Вот только это не всё 66
архитектура (amd64, arm64, mips, …) - *.s - Assembler - 👀 - Значит пойдём разбираться - Посмотрим на asm_amd64.s - всего лишь 2к строк - в остальных + - 1к - arm64.s тоже тяжелый Вот только это не всё 67
when using // internal linking. This is the entry point for the program from the // kernel for an ordinary -buildmode=exe program. The stack holds the // number of arguments and the C-style argv. TEXT _rt0_amd64(SB),NOSPLIT,$-8 MOVQ 0(SP), DI // argc LEAQ 8(SP), SI // argv JMP runtime·rt0_go(SB) Ах вот оно что! 70
stack-based Go ABI (and // in addition there are no calls to this entry point from Go code). TEXT runtime·rt0_go<ABIInternal>(SB),NOSPLIT,$0 // copy arguments forward on an even stack MOVQ DI, AX // argc MOVQ SI, BX // argv // ... // create istack out of the given (operating system) stack. MOVQ $runtime·g0(SB), DI // ... // find out information about the processor we're on MOVL $0, AX CPUID // ... Пойдем дальше 71
serialize RDTSC. // On Intel processors LFENCE is enough. AMD requires MFENCE. // Don't know about the rest, so let's do MFENCE. // ... ok: // set the per-goroutine and per-mach "registers" get_tls(BX) LEAQ runtime·g0(SB), CX MOVQ CX, g(BX) LEAQ runtime·m0(SB), AX // save m->g0 = g0 MOVQ CX, m_g0(AX) // save m0 to g0->m MOVQ AX, g_m(CX)
таймеры - Рандом - Размеры тредов и прочих системных вещей - Энвы и аргументы программы - речь о environment variables и argc & argv runtime/os_<os>.go 76
таймеры - Рандом - Размеры тредов и прочих системных вещей - Энвы и аргументы программы - речь о environment variables и argc & argv - Проверка адекватности машины - o! runtime/os_<os>.go 77
проверяем размеры примитивных типов - d uint16 - if unsafe.Sizeof(d) != 2 { throw("bad d") } - check() вызывает testAtomic64() Проверка адекватности машины 80
проверяем размеры примитивных типов - d uint16 - if unsafe.Sizeof(d) != 2 { throw("bad d") } - check() вызывает testAtomic64() - Как можно догадаться, testAtomic64 про атомики - test_z64 = 42, test_x64 = 0 - if atomic.Cas64(&test_z64, test_x64, 1) { throw("cas64 failed") } Проверка адекватности машины 81
argc = c argv = v sysargs(c, v) } // os_darwin.go func sysargs(argc int32, argv **byte) { // skip over argv, envv and the first string will be the path n := argc + 1 for argv_index(argv, n) != nil { n++ } executablePath = gostringnocopy(argv_index(argv, n+1)) // ... }
func osinit() { // pthread_create delayed until end of goenvs so that we // can look at the environment first. ncpu = getncpu() physPageSize = getPageSize() }
machine. - P - processor, a resource that is required to execute Go code. - getg() - returns the pointer to the current g - _g_ := getg() - p := getg().m.p.ptr() - tls - thread-local storage Остальное это if-ы, атомики и непонятные алгоритмы. Вроде изян. Словарик читателя runtime 90
10000 // maximum number of m's allowed (or die) // The world starts stopped. worldStopped() moduledataverify() // pclntab is correct stackinit() // mallocinit() fastrandinit() // must run before mcommoninit mcommoninit(_g_.m, -1) schedinit /1 91
10000 // maximum number of m's allowed (or die) // The world starts stopped. worldStopped() moduledataverify() // pclntab is correct stackinit() // mallocinit() fastrandinit() // must run before mcommoninit mcommoninit(_g_.m, -1) cpuinit() // must run before alginit alginit() // maps must not be used before this call modulesinit() // provides activeModules typelinksinit() // uses maps, activeModules itabsinit() // uses activeModules schedinit /1 92
// ... if procresize(procs) != nil { throw("unknown runnable goroutine during bootstrap") } unlock(&sched.lock) // World is effectively started now, as P's can run. worldStarted() // ... } schedinit /2 93
start program MOVQ $runtime·mainPC(SB), AX // entry PUSHQ AX PUSHQ $0 // arg size CALL runtime·newproc(SB) POPQ AX POPQ AX ... // mainPC is a function value for runtime.main, to be passed to newproc. // The reference to runtime.main is made via ABIInternal, since the // actual function (not the ABI0 wrapper) is needed by newproc. DATA runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB) GLOBL runtime·mainPC(SB),RODATA,$8 Еще чуть-чуть Ассемблера 94
create a new goroutine to start program MOVQ $runtime·mainPC(SB), AX // entry PUSHQ AX PUSHQ $0 // arg size CALL runtime·newproc(SB) POPQ AX POPQ AX // start this M CALL runtime·mstart(SB) CALL runtime·abort(SB)// mstart should never return RET TEXT runtime·mstart(SB),NOSPLIT|TOPFRAME,$0 CALL runtime·mstart0(SB) RET // not reached
siz bytes of arguments. // Put it on the queue of g's waiting to run. // The compiler turns a go statement into a call to this. // ... func newproc(siz int32, fn *funcval) { argp := add(unsafe.Pointer(&fn), sys.PtrSize) gp := getg() pc := getcallerpc() systemstack(func() { newg := newproc1(fn, argp, siz, gp, pc) _p_ := getg().m.p.ptr() runqput(_p_, newg, true) if mainStarted { wakep() } }) }
of arguments. // Put it on the queue of g's waiting to run. // The compiler turns a go statement into a call to this. // ... func newproc(siz int32, fn *funcval) { argp := add(unsafe.Pointer(&fn), sys.PtrSize) gp := getg() pc := getcallerpc() systemstack(func() { newg := newproc1(fn, argp, siz, gp, pc) _p_ := getg().m.p.ptr() runqput(_p_, newg, true) if mainStarted { wakep() } }) } newproc 97
siz bytes of arguments. // Put it on the queue of g's waiting to run. // The compiler turns a go statement into a call to this. // ... func newproc(siz int32, fn *funcval) { argp := add(unsafe.Pointer(&fn), sys.PtrSize) gp := getg() pc := getcallerpc() systemstack(func() { newg := newproc1(fn, argp, siz, gp, pc) _p_ := getg().m.p.ptr() runqput(_p_, newg, true) if mainStarted { wakep() } }) }
// If systemstack is called from the per-OS-thread (g0) stack, or // if systemstack is called from the signal handling (gsignal) stack, // systemstack calls fn directly and returns. // // Otherwise, systemstack is being called from the limited stack // of an ordinary goroutine. In this case, systemstack switches // to the per-OS-thread stack, calls fn, and switches back. // //go:noescape func systemstack(fn func())
goroutine and execute it. // Never returns. func schedule() { _g_ := getg() top: pp := _g_.m.p.ptr() pp.preempt = false var gp *g // get some gp execute(gp, inheritTime) }
runtime-овые вещи (кэп) - управлением стеком - выполнение defer - различные паники - помощь для дебага - любимый cgo - gc barriers Оке, а почему там 2к линий? 107
runtime-овые вещи (кэп) - управлением стеком - выполнение defer - различные паники - помощь для дебага - любимый cgo - gc barriers - таймеры - сигналы - хеши Оке, а почему там 2к линий? 108
можно найти похожие строки - x86HasSSE41 = cpu.X86.HasSSE41 - на самом деле их сильно больше - Из-за чего мы теряем такты процессора - каждая наносекунда на счету :( GOAMD64 proposal 110
это просто проверки машины - какие размеры типов - какие флаги у процессора - Но есть 1 интересный в runtime (потом обсудим некоторые пакеты из stdlib) Так что там в init()-ах?? 112
goroutine func init() { go forcegchelper() } // 5k lines later... // forcegcperiod is the maximum time in nanoseconds between garbage // collections. If we go this long without a garbage collection, one // is forced to run. // // This is a variable for testing purposes. It normally doesn't change. var forcegcperiod int64 = 2 * 60 * 1e9
импортах мы делаем: import ( _ "crypto/sha256" // to register a sha256 _ "crypto/sha512" // to register a sha384/512 ) // а НЕ это: import ( _ "crypto/sha256" //nolint "crypto/sha512" ) var _ = sha512.<Something_Just_To_Import> Так что там в crypto/* 119
// +build netcgo package net // The build tag "netcgo" forces use of the cgo DNS resolver. // It is the opposite of "netgo". func init() { netCgo = true } // net/conf.go netGo bool // go DNS resolution forced netCgo bool // cgo DNS resolution forced
// +build netcgo package net // The build tag "netcgo" forces use of the cgo DNS resolver. // It is the opposite of "netgo". func init() { netCgo = true } // net/conf.go netGo bool // go DNS resolution forced netCgo bool // cgo DNS resolution forced
flag lets you run // go test -run=BrokenTest -httptest.serve=127.0.0.1:8000 // to start the broken server so you can interact with it manually. // ... // isn't really part of our API. Don't depend on this. var serveFlag string func init() { if has(os.Args, "-httptest.serve=") { flag.StringVar(&serveFlag, "httptest.serve", "", "...") } } Так что там в net/http/httptest 124
// mime/type_openbsd.go func init() { typeFiles = append(typeFiles, "/usr/share/misc/mime.types") } ... Так что там в mime 126 MIME == Multipurpose Internet Mail Extensions
p := range oldPools { // ... } for _, p := range allPools { // ... } // The pools with non-empty primary caches now have non-empty // victim caches and no pools have primary caches. oldPools, allPools = allPools, nil } Так что там в sync 127
database. // If this package is imported anywhere in the program, then if // the time package cannot find tzdata files on the system, // it will use this embedded information. // // Importing this package will increase the size of a program by about // 450 KB. // Так что там в time/tzdata 130
database. // If this package is imported anywhere in the program, then if // the time package cannot find tzdata files on the system, // it will use this embedded information. // // Importing this package will increase the size of a program by about // 450 KB. // Так что там в time/tzdata 131
an embedded copy of the timezone database. // If this package is imported anywhere in the program, then if // the time package cannot find tzdata files on the system, // it will use this embedded information. // // Importing this package will increase the size of a program by about // 450 KB. // // This package should normally be imported by a program's main package, // not by a library. Libraries normally shouldn't decide whether to // include the timezone database in a program. // // This package will be automatically imported if you build with // -tags timetzdata. package tzdata
an embedded copy of the timezone database. // If this package is imported anywhere in the program, then if // the time package cannot find tzdata files on the system, // it will use this embedded information. // // Importing this package will increase the size of a program by about // 450 KB. // // This package should normally be imported by a program's main package, // not by a library. Libraries normally shouldn't decide whether to // include the timezone database in a program. // // This package will be automatically imported if you build with // -tags timetzdata. package tzdata
ведь помним, что когда-то Go был написан на Си :) - Также достаточное количество _ в именах переменных и функций - опять же наследие Си Интересности в 1 слайд 136
ведь помним, что когда-то Go был написан на Си :) - Также достаточное количество _ в именах переменных и функций - опять же наследие Си - Windows, NetBSD, Wasm, JS и Plan9 особенные - часто из-за них есть осознанные костыли Интересности в 1 слайд 137
ведь помним, что когда-то Go был написан на Си :) - Также достаточное количество _ в именах переменных и функций - опять же наследие Си - Windows, NetBSD, Wasm, JS и Plan9 особенные - часто из-за них есть осознанные костыли - Cgo is not Go, как говорил Пайк - и этим все сказано Интересности в 1 слайд 138
ведь помним, что когда-то Go был написан на Си :) - Также достаточное количество _ в именах переменных и функций - опять же наследие Си - Windows, NetBSD, Wasm, JS и Plan9 особенные - часто из-за них есть осознанные костыли - Cgo is not Go, как говорил Пайк - и этим все сказано - Runtime is all about corner cases - такое моё ИМХО Интересности в 1 слайд 139
The Go Memory Model - https://golang.org/ref/mem - Go 1.2 Runtime Symbol Information (2013) - https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZO z_o/pub - runtime/HACKING.md - https://github.com/golang/go/blob/master/src/runtime/HACKING.md Материалы 142