Upgrade to Pro — share decks privately, control downloads, hide ads and more …

聊聊 Cgo 的二三事

David Chou
August 02, 2022

聊聊 Cgo 的二三事

David Chou

August 02, 2022
Tweet

More Decks by David Chou

Other Decks in Technology

Transcript

  1. @ Crescendo Lab @ Golang Taipei Co-organizer Software engineer, DevOps,

    and Gopher david74.chou @ gmail david74.chou @ facebook david74chou @ telegram
  2. package main /* #include <stdlib.h> */ import "C" import "fmt"

    func Random() int { r := C.random() // r is C.long return int(r) } func Seed(i int) { C.srandom(C.uint(i)) } func main() { Seed(0) for i := 0; i < 10; i++ { fmt.Printf("rand(%d): %d\n", i, Random()) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /* #include <stdlib.h> */ import "C" package main 1 2 3 4 5 6 import "fmt" 7 8 func Random() int { 9 r := C.random() // r is C.long 10 return int(r) 11 } 12 13 func Seed(i int) { 14 C.srandom(C.uint(i)) 15 } 16 17 func main() { 18 Seed(0) 19 20 for i := 0; i < 10; i++ { 21 fmt.Printf("rand(%d): %d\n", i, Random()) 22 } 23 } 24 r := C.random() // r is C.long return int(r) C.srandom(C.uint(i)) package main 1 2 /* 3 #include <stdlib.h> 4 */ 5 import "C" 6 import "fmt" 7 8 func Random() int { 9 10 11 } 12 13 func Seed(i int) { 14 15 } 16 17 func main() { 18 Seed(0) 19 20 for i := 0; i < 10; i++ { 21 fmt.Printf("rand(%d): %d\n", i, Random()) 22 } 23 } 24
  3. // #cgo CFLAGS: -DPNG_DEBUG=1 // #cgo amd64 386 CFLAGS: -DX86=1

    // #cgo LDFLAGS: -lpng // #include <png.h> import "C" Cgo Preamble CFLAGS / CPPFLAGS / LDFLAGS Add build constraints
  4. // #cgo pkg-config: png // #cgo CFLAGS: -DPNG_DEBUG=1 // #include

    <png.h> import "C" Cgo Preamble Use pkg-config tool
  5. package native /* #cgo CFLAGS: -I${SRCDIR}/includes/ #cgo windows LDFLAGS: ${SRCDIR}/libs/winlibz.a

    #cgo linux LDFLAGS: ${SRCDIR}/libs/linuxlibz.a #cgo darwin LDFLAGS: ${SRCDIR}/libs/darwinlibz.a */ import "C" . ├── cgo.go ├── libs │ ├── darwinlibz.a │ ├── linuxlibz.a │ └── winlibz.a └── includes ├── zconf.h └── zlib.h
  6. C types in Cgo char => C.char unsigned char =>

    C.uchar int => C.int void* => unsafe.Pointer struct S => C.struct_S enum E => C.enum_E
  7. Data conversion in Cgo func C.CString(string) *C.char func C.CBytes([]byte) unsafe.Pointer

    func C.free(unsafe.Pointer) Copy Go string / []byte to C Allocated with malloc() Must be freed with C.free()
  8. Data conversion in Cgo func C.GoString(*C.char) string func C.GoStringN(*C.char, C.int)

    string func C.GoBytes(unsafe.Pointer, C.int) []byte Copy C string / bytes to Go
  9. /* #include <stdlib.h> char *get_string() { return strdup("hello from C");

    } void release_string(char *s) { free(s); } */ import "C" func main() { cs := C.CString("Hello from go\n") C.puts(cs) C.free(unsafe.Pointer(cs)) cs = C.get_string() gs := C.GoString(cs) fmt.Printf("%s\n", gs) C.release_string(cs) }
  10. Why not to use Cgo Break Go’s awesome tooling Break

    your static binary Break cross-compiling Manage memory in C by hand Cgo calls are much slower than native Go calls
  11. //#include <unistd.h> //void foo() { } import "C" //go:noinline func

    foo() {} func CallCgo(n int) { for i := 0; i < n; i++ { C.foo() } } func CallGo(n int) { for i := 0; i < n; i++ { foo() } } BenchmarkCGO-16 3772150 308.3 ns/op BenchmarkGo-16 931552690 1.231 ns/op
  12. When to use Cgo No Go equivalent library Integrate with

    legacy C code Need to consume a proprietary library in C
  13. Write your own C bridge func Get more control with

    your C interface More precise in C header Merge multiple Cgo calls into 1 Do loop-calls in C is more efficient foos := []C.struct_Foo{ { a: C.int(1), b: C.int(2) }, { a: C.int(3), b: C.int(4) }, } C.pass_array((*C.struct_Foo)(unsafe.Pointer(&foos[0])), C.int(len(foos)))
  14. Isolate the Cgo part Isolate the Cgo wrapper into its

    own package even in a standalone process and communicate with RPC Can't call Cgo in Go test code
  15. Use static link library Try hard to static link your

    libraries, otherwise your dependency might be complicated go build -ldflags='-extldflags "-static"'
  16. Be careful for memory mgmt Never keep any pointer outside

    of the called function the memory might be GC or free
  17. Be careful for memory mgmt Be aware of the C-memory

    ownership Use "defer C.free()" to release memory when func returns d := C.GoBytes([]byte{...}) C.foo(d) // <- if foo() takes the ownership // C.free(d)
  18. Be careful for memory mgmt ZeroCopy: Go => C func

    Foo(payload []byte) { p := (*C.uchar)(unsafe.Pointer(&payload[0])) size := C.int(len(payload)) C.foo(p, size) } func Foo(str string) { strHdr := (*reflect.StringHeader)(unsafe.Pointer(&str)) p := (*C.char)(unsafe.Pointer(strHdr.Data)) size := C.int(len(payload)) C.foo(p, size) } ZeroCopy (Go => C): []bytes ZeroCopy (Go => C): string
  19. Cgoroutines != Goroutines Cgo calls block your system thread func

    main() { var wg sync.WaitGroup wg.Add(1000) for i := 0; i < 1000; i++ { go func() { time.Sleep(100 * time.Second) wg.Done() }() } wg.Wait() } func main() { var wg sync.WaitGroup wg.Add(1000) for i := 0; i < 1000; i++ { go func() { time.Sleep(100 * time.Second) wg.Done() }() } wg.Wait() } // use 13 threads // use 1004 threads
  20. Cgoroutines != Goroutines // Call from Go to C. func

    cgocall(fn, arg unsafe.Pointer) int32 { ... // Announce we are entering a system call // so that the scheduler knows to create another // M to run goroutines while we are in the // foreign code. // entersyscall() errno := asmcgocall(fn, arg) exitsyscall() ... }
  21. C callbacks to Go Go function could be exported for

    use by C //export Add func Add(arg1, arg2 int) int {...} It will be available in the C code as extern int Add(int arg1, int arg2);
  22. C callbacks to Go C can't callbacks to a receiver

    function because of Cgo pointer passing rule need to be creative func (c *MyCounter) Add(i int) { c.count += i }
  23. var mu sync.Mutex var index int var fns = make(map[int]func(int))

    func register(fn func(int)) int { mu.Lock() defer mu.Unlock() index++ for fns[index] != nil { index++ } fns[index] = fn return index } func lookup(i int) func(int) { ... } func unregister(i int) { ... } Use an extra map to keep the callbacks
  24. /* extern void go_callback_int( int foo, int p1); static inline

    void CallMyFunction(int foo) { go_callback_int(foo, 5); } */ import "C" //export go_callback_int func go_callback_int(idx C.int, p1 C.int) { fn := lookup(int(idx)) fn(int(p1)) } func (c *MyCounter) Add(i int) { ... } func main() { c := &MyCounter{count: 0} i := register(c.Add) C.CallMyFunction(C.int(i)) unregister(i) }
  25. /* extern void go_callback_int( uintptr_t h, int p1); static inline

    void CallMyFunction(uintptr_t h) { go_callback_int(h, 5); } */ import "C" //export go_callback_int func go_callback_int(h C.uintptr_t, p C.int){ v := cgo.Handle(h).Value() fn := v.(func(C.int)) fn(p) } func (c *MyCounter) Add(i int) { ... } func main() { c := &MyCounter{count: 0} i := register(c.Add) h := cgo.NewHandle(c.Add) C.CallMyFunction(C.uintptr_t(h)) h.Delete() } Go 1.17 cgo.Handle
  26. Cgo preamble C <=> Go type conversion When to /

    not-to use Cgo Cgo tips and pitfalls Recap
  27. We are hiring now! Feel free to chat with us

    in Room TR 312 ( 研揚⼤樓 312 室).