├── .gitignore ├── LICENSE ├── README.md ├── emptycall ├── README.md ├── c │ └── emptycall_bench.c ├── cgo.go ├── emptycall_test.go ├── go.go └── run.sh └── syscall ├── README.md ├── c └── syscall_bench.c ├── cgo-go-c.png ├── run.sh ├── syscall_cgo.go └── syscall_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ou Changkun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cgo benchmarks 2 | 3 | This repository provides a series of benchmarks between Cgo, Go and C. 4 | 5 | ## Benchmark: CGO vs GO vs C in Empty Calls 6 | 7 | ### Results 8 | 9 | ``` 10 | goos: darwin 11 | goarch: amd64 12 | pkg: github.com/changkun/cgo-benchmarks/emptycall 13 | BenchmarkEmptyCgoCalls-4 20000000 55.9 ns/op 14 | BenchmarkEmptyGoCalls-4 2000000000 0.29 ns/op 15 | BenchmarkEmptyCCalls 2000000000 00 ns/op 16 | PASS 17 | ok github.com/changkun/cgo-benchmarks/emptycall 1.807s 18 | ``` 19 | 20 | ### Conclusions 21 | 22 | - Pure Go call is `(55.9 - 0.29) / 0.29 = 191.76%` faster than Cgo call. 23 | - Pure C call is `(0.29 - 0.00) / 0.00 = infinity` faster than Go call. 24 | - Pure C call is `(55.9 - 0.00) / 0.00 = infinity` faster than Cgo call. 25 | 26 | ### Related researches 27 | 28 | - https://github.com/draffensperger/go-interlang 29 | 30 | ## Benchmark: CGO vs GO vs C in System Calls `read/write` 31 | 32 | ![](syscall/cgo-go-c.png) 33 | 34 | ### Results 35 | 36 | ``` 37 | goos: darwin 38 | goarch: amd64 39 | pkg: github.com/changkun/cgo-benchmarks/syscall 40 | BenchmarkReadWriteCgoCalls-4 500000 3532 ns/op 41 | BenchmarkReadWriteGoCalls-4 500000 2599 ns/op 42 | BenchmarkReadWritePureCCalls 500000 2244 ns/op 43 | PASS 44 | ok github.com/changkun/cgo-benchmarks/syscall 3.142s 45 | ``` 46 | 47 | ### Conclusions 48 | 49 | - Pure Go system call is `(3532 - 2599) / 2599 = 35.90%` faster than Cgo call. 50 | - Pure C system call is `(2599 - 2244) / 2244 = 15.82%` faster than Go system call. 51 | - Pure C system call is `(3532 - 2244) / 2244 = 57.40%` faster than Cgo system call. 52 | 53 | ### Related researches 54 | 55 | - https://github.com/golang/go/issues/19563 56 | - https://github.com/golang/go/issues/19574 57 | 58 | ### Benchmark: TODO 59 | 60 | 61 | ## `go env` 62 | 63 | ``` 64 | GOARCH="amd64" 65 | GOBIN="" 66 | GOCACHE="/Users/changkun/Library/Caches/go-build" 67 | GOEXE="" 68 | GOFLAGS="" 69 | GOHOSTARCH="amd64" 70 | GOHOSTOS="darwin" 71 | GOOS="darwin" 72 | GOPATH="/Users/changkun/dev/golang" 73 | GOPROXY="" 74 | GORACE="" 75 | GOROOT="/usr/local/Cellar/go/1.11/libexec" 76 | GOTMPDIR="" 77 | GOTOOLDIR="/usr/local/Cellar/go/1.11/libexec/pkg/tool/darwin_amd64" 78 | GCCGO="gccgo" 79 | CC="clang" 80 | CXX="clang++" 81 | CGO_ENABLED="1" 82 | GOMOD="" 83 | CGO_CFLAGS="-g -O2" 84 | CGO_CPPFLAGS="" 85 | CGO_CXXFLAGS="-g -O2" 86 | CGO_FFLAGS="-g -O2" 87 | CGO_LDFLAGS="-g -O2" 88 | PKG_CONFIG="pkg-config" 89 | GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/61/r39b4cjx2bggk1_p7pmpbrdw0000gn/T/go-build318232985=/tmp/go-build -gno-record-gcc-switches -fno-common" 90 | ``` 91 | 92 | ## License 93 | 94 | MIT © [Changkun Ou](https://changkun.de) -------------------------------------------------------------------------------- /emptycall/README.md: -------------------------------------------------------------------------------- 1 | # Benchmark: CGO vs GO vs C in Empty Calls 2 | 3 | ## Run 4 | 5 | ```bash 6 | sh run.sh 7 | ``` 8 | 9 | ## Results 10 | 11 | ``` 12 | goos: darwin 13 | goarch: amd64 14 | pkg: github.com/changkun/cgo-benchmarks/emptycall 15 | BenchmarkEmptyCgoCalls-4 20000000 55.9 ns/op 16 | BenchmarkEmptyGoCalls-4 2000000000 0.29 ns/op 17 | BenchmarkEmptyCCalls 2000000000 00 ns/op 18 | PASS 19 | ok github.com/changkun/cgo-benchmarks/emptycall 1.807s 20 | ``` 21 | 22 | ## Conclusions 23 | 24 | - Pure Go call is `(55.9 - 0.29) / 0.29 = 191.76%` faster than Cgo call. 25 | - Pure C call is `(0.29 - 0.00) / 0.00 = infinity` faster than Go call. 26 | - Pure C call is `(55.9 - 0.00) / 0.00 = higher infinity` faster than Cgo call. 27 | 28 | ## Related researches 29 | 30 | - https://github.com/draffensperger/go-interlang 31 | 32 | ## License 33 | 34 | MIT © [Changkun Ou](https://changkun.de) -------------------------------------------------------------------------------- /emptycall/c/emptycall_bench.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // call this function to start a nanosecond-resolution timer 5 | struct timespec timer_start(){ 6 | struct timespec start_time; 7 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time); 8 | return start_time; 9 | } 10 | 11 | // call this function to end a timer, returning nanoseconds elapsed as a long 12 | long timer_end(struct timespec start_time){ 13 | struct timespec end_time; 14 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_time); 15 | long diffInNanos = (end_time.tv_sec - start_time.tv_sec) * (long)1e9 + (end_time.tv_nsec - start_time.tv_nsec); 16 | return diffInNanos; 17 | } 18 | 19 | void empty() {} 20 | 21 | int main() { 22 | int i = 0; 23 | long N = 1000000000; 24 | struct timespec vartime = timer_start(); 25 | for(i = 0; i < N; i++) { 26 | empty(); 27 | } 28 | long time_elapsed_nanos = timer_end(vartime); 29 | printf("BenchmarkEmptyCCalls\t%ld\t%.2f ns/op\n", N, ((double)time_elapsed_nanos)/N); 30 | } -------------------------------------------------------------------------------- /emptycall/cgo.go: -------------------------------------------------------------------------------- 1 | package emptycall 2 | 3 | /* 4 | void empty() { 5 | } 6 | */ 7 | import "C" 8 | 9 | // Cempty is an empty cgo call 10 | func Cempty() { 11 | C.empty() 12 | } 13 | -------------------------------------------------------------------------------- /emptycall/emptycall_test.go: -------------------------------------------------------------------------------- 1 | package emptycall_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/changkun/cgo-benchmarks/emptycall" 7 | ) 8 | 9 | func BenchmarkEmptyCgoCalls(b *testing.B) { 10 | for i := 0; i < b.N; i++ { 11 | emptycall.Cempty() 12 | } 13 | } 14 | 15 | func BenchmarkEmptyGoCalls(b *testing.B) { 16 | for i := 0; i < b.N; i++ { 17 | emptycall.Empty() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /emptycall/go.go: -------------------------------------------------------------------------------- 1 | package emptycall 2 | 3 | // Empty is an empty go call 4 | func Empty() {} 5 | -------------------------------------------------------------------------------- /emptycall/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | gcc -O2 c/emptycall_bench.c -o empty_c 4 | go test -bench=. -count=1000 5 | 6 | for i in `seq 1 1000`; do 7 | ./empty_c 8 | done 9 | rm empty_c -------------------------------------------------------------------------------- /syscall/README.md: -------------------------------------------------------------------------------- 1 | # Benchmark: CGO vs GO vs C in System Calls `read/write` 2 | 3 | ## Run 4 | 5 | ```bash 6 | sh run.sh 7 | ``` 8 | 9 | ## Results 10 | 11 | ![](cgo-go-c.png) 12 | 13 | see [data](https://docs.google.com/spreadsheets/d/1DwtZmP8fKKr3pOQWVJrD30DSOzv4_qB5KZvsd-DQ1KA/edit?usp=sharing). 14 | 15 | ``` 16 | goos: darwin 17 | goarch: amd64 18 | pkg: github.com/changkun/cgo-benchmarks/syscall 19 | BenchmarkReadWriteCgoCalls-4 500000 3532 ns/op 20 | BenchmarkReadWriteGoCalls-4 500000 2599 ns/op 21 | BenchmarkReadWriteNetCalls-8 500000 2890 ns/op 22 | BenchmarkReadWritePureCCalls 500000 2244 ns/op 23 | PASS 24 | ok github.com/changkun/cgo-benchmarks/syscall 3.142s 25 | ``` 26 | 27 | ## Conclusions 28 | 29 | - Pure Go system call is `(3532 - 2599) / 2599 = 35.90%` faster than Cgo call. 30 | - Pure C system call is `(2599 - 2244) / 2244 = 15.82%` faster than Go system call. 31 | - Pure C system call is `(3532 - 2244) / 2244 = 57.40%` faster than Cgo system call. 32 | 33 | ## Related researches 34 | 35 | - https://github.com/golang/go/issues/19563 36 | - https://github.com/golang/go/issues/19574 37 | 38 | ## License 39 | 40 | MIT © [Changkun Ou](https://changkun.de) -------------------------------------------------------------------------------- /syscall/c/syscall_bench.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int write_all(int fd, void* buffer, size_t length) { 7 | while (length > 0) { 8 | int written = write(fd, buffer, length); 9 | if (written < 0) 10 | return -1; 11 | length -= written; 12 | buffer += written; 13 | } 14 | return length; 15 | } 16 | 17 | int read_call(int fd, void *buffer, size_t length) { 18 | return read(fd, buffer, length); 19 | } 20 | 21 | struct timespec timer_start(){ 22 | struct timespec start_time; 23 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time); 24 | return start_time; 25 | } 26 | 27 | long timer_end(struct timespec start_time){ 28 | struct timespec end_time; 29 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_time); 30 | long diffInNanos = (end_time.tv_sec - start_time.tv_sec) * (long)1e9 + (end_time.tv_nsec - start_time.tv_nsec); 31 | return diffInNanos; 32 | } 33 | 34 | int main() { 35 | int i = 0; 36 | int N = 500000; 37 | int fds[2]; 38 | char message[14] = "hello, world!\0"; 39 | char buffer[14] = {0}; 40 | 41 | socketpair(AF_UNIX, SOCK_STREAM, 0, fds); 42 | struct timespec vartime = timer_start(); 43 | for(i = 0; i < N; i++) { 44 | write_all(fds[0], message, sizeof(message)); 45 | read_call(fds[1], buffer, 14); 46 | } 47 | long time_elapsed_nanos = timer_end(vartime); 48 | printf("BenchmarkReadWritePureCCalls\t%d\t%.2ld ns/op\n", N, time_elapsed_nanos/N); 49 | } -------------------------------------------------------------------------------- /syscall/cgo-go-c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changkun/cgo-benchmarks/c7272a9b5813846e3d60aba91e4714aeb06d8fd7/syscall/cgo-go-c.png -------------------------------------------------------------------------------- /syscall/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | gcc -O2 c/syscall_bench.c -o syscall_pure_c 3 | go test -bench=. -count=5 -timeout 20m -v 4 | for i in `seq 1 5`; do 5 | ./syscall_pure_c 6 | done 7 | rm syscall_pure_c -------------------------------------------------------------------------------- /syscall/syscall_cgo.go: -------------------------------------------------------------------------------- 1 | package syscall 2 | 3 | /* 4 | #include 5 | int write_all(int fd, void* buffer, size_t length) { 6 | while (length > 0) { 7 | int written = write(fd, buffer, length); 8 | if (written < 0) 9 | return -1; 10 | length -= written; 11 | buffer += written; 12 | } 13 | return length; 14 | } 15 | int read_call(int fd, void *buffer, size_t length) { 16 | return read(fd, buffer, length); 17 | } 18 | */ 19 | import "C" 20 | import ( 21 | "unsafe" 22 | ) 23 | 24 | // CwriteAll is a cgo call for write 25 | func CwriteAll(fd int, buf []byte) error { 26 | _, err := C.write_all(C.int(fd), unsafe.Pointer(&buf[0]), C.size_t(len(buf))) 27 | return err 28 | } 29 | 30 | // Cread is a cgo call for read 31 | func Cread(fd int, buf []byte) (int, error) { 32 | ret, err := C.read_call(C.int(fd), unsafe.Pointer(&buf[0]), C.size_t(len(buf))) 33 | return int(ret), err 34 | } 35 | -------------------------------------------------------------------------------- /syscall/syscall_test.go: -------------------------------------------------------------------------------- 1 | package syscall 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "syscall" 7 | "testing" 8 | ) 9 | 10 | const message = "hello, world!" 11 | 12 | var buffer = make([]byte, 13) 13 | 14 | func writeAll(fd int, buf []byte) error { 15 | for len(buf) > 0 { 16 | n, err := syscall.Write(fd, buf) 17 | if n < 0 { 18 | return err 19 | } 20 | buf = buf[n:] 21 | } 22 | return nil 23 | } 24 | 25 | func BenchmarkReadWriteCgoCalls(b *testing.B) { 26 | fds, _ := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) 27 | for i := 0; i < b.N; i++ { 28 | CwriteAll(fds[0], []byte(message)) 29 | Cread(fds[1], buffer) 30 | } 31 | } 32 | 33 | func BenchmarkReadWriteGoCalls(b *testing.B) { 34 | fds, _ := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) 35 | for i := 0; i < b.N; i++ { 36 | writeAll(fds[0], []byte(message)) 37 | syscall.Read(fds[1], buffer) 38 | } 39 | } 40 | 41 | func BenchmarkReadWriteNetCalls(b *testing.B) { 42 | cs, _ := socketpair() 43 | for i := 0; i < b.N; i++ { 44 | cs[0].Write([]byte(message)) 45 | cs[1].Read(buffer) 46 | } 47 | } 48 | 49 | func socketpair() (conns [2]net.Conn, err error) { 50 | fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0) 51 | if err != nil { 52 | return 53 | } 54 | conns[0], err = fdToFileConn(fds[0]) 55 | if err != nil { 56 | return 57 | } 58 | conns[1], err = fdToFileConn(fds[1]) 59 | if err != nil { 60 | conns[0].Close() 61 | return 62 | } 63 | return 64 | } 65 | 66 | func fdToFileConn(fd int) (net.Conn, error) { 67 | f := os.NewFile(uintptr(fd), "") 68 | defer f.Close() 69 | return net.FileConn(f) 70 | } 71 | --------------------------------------------------------------------------------