New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: _cgoCheckPointer0 extreme overhead #14265

Open
tgulacsi opened this Issue Feb 8, 2016 · 2 comments

Comments

Projects
None yet
3 participants
@tgulacsi

tgulacsi commented Feb 8, 2016

  1. What version of Go are you using (go version)?
    go1.6rc2
  2. What operating system and processor architecture are you using?
    linux/amd64
  3. What did you do?
    Call C code with cgo.
  4. What did you expect to see?
    Almost the same performance with GODEBUG=cgocheck=1 and GODEBUG=cgocheck=0.
  5. What did you see instead?
    22 times more running time with GODEBUG=cgocheck=1 than with GODEBUG=cgocheck=0.

The overhead is 22x in practice and 36000 in this contrived example:

$ GODEBUG=cgocheck=1 go test -bench=. -run=-
testing: warning: no tests to run
PASS
BenchmarkCGO-4 50 28792524 ns/op
ok github.com/tgulacsi/go/cgo22 1.855s
:gthomas@waterhouse: ~/src/github.com/tgulacsi/go/cgo22
$ GODEBUG=cgocheck=0 go test -bench=. -run=-
testing: warning: no tests to run
PASS
BenchmarkCGO-4 2000000 796 ns/op
ok github.com/tgulacsi/go/cgo22 2.988s
:gthomas@waterhouse: ~/src/github.com/tgulacsi/go/cgo22

The code is at https://github.com/tgulacsi/go/tree/master/cgo22

cgo22.zip

@bradfitz bradfitz changed the title from _cgoCheckPointer0 extreme overhead to runtime/cgo: _cgoCheckPointer0 extreme overhead Feb 8, 2016

@bradfitz bradfitz added this to the Unplanned milestone Feb 8, 2016

@ianlancetaylor ianlancetaylor changed the title from runtime/cgo: _cgoCheckPointer0 extreme overhead to runtime: _cgoCheckPointer0 extreme overhead Feb 8, 2016

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented Feb 8, 2016

Thanks for the test case. I don't know how to do much better for this kind of example. The pointer passing rules say that if you pass the address of an element of a slice, the C code is permitted to examine every element of the slice. You are passing the address of a 1000000 slice of pointers, so each call needs to check 1000000 pointers to make sure that they are not Go pointers.

@tgulacsi

This comment has been minimized.

tgulacsi commented Feb 9, 2016

Thanks Ian for the clear description!

Now I think I understand another strange thing: with
s := struct { p C.ub4, q *GoObj }
C.f(&s.p)
does not work, but (!) with
s := struct{ p [1]C.ub4, q *GoObj },
C.f(&s.p[0])
does work - here we encapsulated the referent, so cgo does not check q.

Back to the original issue: this understanding allows a not-too-ugly workaround:

    i := i % int(n)
    carr := arr[i : i+1 : i+1]
    i = (i + 100) % int(n)
    darr := arr[i : i+1 : i+1]
    //C.call(&arr[i%int(n)], &arr[(i+100)%int(n)])
    C.call(&carr[0], &darr[0])

By calling with a one-element-length array (capped), it works:

:tgulacsi@tgulacsi-Aspire-V3-371: ~/src/github.com/tgulacsi/go/cgo22
! GODEBUG=cgocheck=0 go test  -run=X -bench=.
testing: warning: no tests to run
PASS
BenchmarkCGONaive-4      3000000               494 ns/op
BenchmarkCGOSliced-4     3000000               501 ns/op
BenchmarkCGOCapped-4     3000000               507 ns/op
ok      github.com/tgulacsi/go/cgo22    6.400s
:tgulacsi@tgulacsi-Aspire-V3-371: ~/src/github.com/tgulacsi/go/cgo22
$ GODEBUG=cgocheck=1 go test  -run=X -bench=.
testing: warning: no tests to run
PASS
BenchmarkCGONaive-4          100          18490218 ns/op
BenchmarkCGOSliced-4         100          18211666 ns/op
BenchmarkCGOCapped-4     3000000               568 ns/op
ok      github.com/tgulacsi/go/cgo22    6.305s

Here the "Naive" is the old version, the "Sliced" is the carr := arr[i:i+1] version, and the "Capped" is the above carr := arr[i:i+1:i+1] version.

(same repo has the new code).

I think at least we should document it more explicitly, that the cgo checker checks all reachable pointers in the struct/array/slice level.

tgulacsi pushed a commit to rana/ora that referenced this issue Feb 9, 2016

Tamás Gulácsi
Rewrite for Go 1.6's stricter C-Go pointer passing
Go 1.6 has a strict checker to disable passing memory allocated by Go
that contains Go pointers ("Go pointer to Go pointer").
So we have to C.malloc everywhere where we want C to write to memory,
and read it in Go.

See https://tip.golang.org/cmd/cgo/#hdr-Go_references_to_C for the
documentation, https://tip.golang.org/pkg/unsafe/ for the slice tricks,
and https://groups.google.com/forum/#!topic/golang-nuts/gnH0nhPf36I for
the hunt.

Put some cgo helper function in bnd_hlp.go, and some C for cycles in
version.c.

Extra care needed when passing pointers of array/slice elements with
cgo: use the whole array on C side, or move the for cycle to C side.
See golang/go#14265 for details of the cgo
check overhead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment