I have a variable (but capped in combined size) number of GoStrings that need to be passed to C, and I want to do it as cheaply as possible. I'm going to perform this operation many times (so pre-allocating buffers that are reused can be considered zero cost).
My initial approach was to loop over the GoStrings, converting each to a CString and pushing it to C.
for _, str := range mystrings {
cstr := C.CString(str)
defer C.free(unsafe.Pointer(cstr))
C.push_str(pushStrFn, cstr)
}
Of course, this is performing N heap allocations thanks to C.CString
, as well as N CGo invocations - all of which are not cheap.
Next up was to build a single big string in Go using an allocated-at-the-beginning-of-time strings.Builder
, and then pass it to C with some length information in a single CGo call. This is one CString invocation, and one CGo call - a substantial improvement.
builder.Reset()
for _, str := range mystrings {
builder.WriteString(str)
}
C.push_strs(pushStrsFn, C.CString(builder.String()))
But this approach is still performing an unnecessary copy! Ideally I'd like to pre-allocate a big chunk of memory that I can pass to C, and just copy the strings directly to it without using a big GoString intermediary.
I'm able to pre-allocate a big array ahead of time, and iterate over the characters in the GoStrings, copying them over one at a time. This avoids the intermediate copy, but is substantially slower than a dedicated string-copying function (like that of the builder).
cCharArray := C.malloc(C.size_t(MAX_SIZE) * C.size_t(unsafe.Sizeof(uintptr(0))))
goCharArray := (*[1<<30 - 1]C.char)(cCharArray)
for _, str := range mystrings {
for i, c := range str {
goCharArray[offset i] = C.char(c)
}
}
C.push_charArray(pushCharArrayFn, (*C.char)(cCharArray))
Is there a faster way to do this that I'm missing? Can I somehow feed a C buffer to strings.Builder
, or use a string-copying function directly to the C buffer?
CodePudding user response:
Did you try out C.strncpy
, or this wrapper over it ? https://github.com/chai2010/cgo/blob/master/char.go#L61 . If this is not faster you could try to cast the pointer to the Go string into a CString pointer using unsafe.