有没有一种方法可以直接将Golang字符串复制到预分配的C char缓冲区中?

3

我有一个变量(但大小受限)数量的GoStrings需要传递给C,我希望尽可能地节省成本。 我将执行此操作多次(因此可以认为预分配重复使用的缓冲区的成本为零)。

我的初始方法是循环遍历GoStrings,将每个转换为CString并将其推送到C。

for _, str := range mystrings {
   cstr := C.CString(str)
   defer C.free(unsafe.Pointer(cstr))
   C.push_str(pushStrFn, cstr)
}

当然,这需要使用C.CString进行N次堆分配以及N次CGo调用,这些操作都不是很便宜。

接下来的做法是在Go中使用一个一开始就被分配的strings.Builder构建一个单独的大字符串,并通过一个CGo调用将其传递给C语言并附带长度信息。这样只需要进行一次CString调用和一次CGo调用,大大提升了性能。

builder.Reset()
for _, str := range mystrings {
   builder.WriteString(str)
}
C.push_strs(pushStrsFn, C.CString(builder.String()))

但这种方法仍然进行了不必要的复制!理想情况下,我想预先分配一个大块内存,可以将其传递给C,并直接将字符串复制到其中,而无需使用大的GoString中介。

我能够预先分配一个大数组,并遍历GoStrings中的字符,逐个复制它们。这避免了中间复制,但比专用的字符串复制函数(如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))

有没有更快的方法来完成这个任务?我是否可以将C缓冲区传递给strings.Builder,或者直接使用字符串复制函数来操作C缓冲区?


你确定你的架构是正确的吗?Go-C接口本身很慢。 - rustyx
我知道,这很痛苦,也不是我处理这个问题的首选方式,但生活就是这样。 - Locke
2个回答

1

不幸的是,C.strncpy包装器本身正在进行CGo调用,这是我在这里试图避免的较大瓶颈之一。话虽如此,通过foo := (*C.char)(unsafe.Pointer(&[]byte(builder.String())[0]))对GoString进行转换的强制转换效果非常好!看起来它大约可以缩短300纳秒的端到端时间,这太棒了。 - Locke
1
@Locke:请注意,您的解决方案依赖于strings.Builder的内部实现细节,并不能保证安全或有效。也许bytes.Buffer更适合您,但是在将指针发送到C代码后,支持数组变得无效的问题仍然存在,因此管理该缓冲区的生命周期至关重要。 - JimB

0
使用unsafe包将C分配的缓冲区创建为字节切片。
然后,您可以直接复制到该切片中,或将其作为空长度重新切片并粘贴到bytes.Buffer中,并使用WriteString。您必须小心不要超出原始容量,否则Buffer将会将您的数据复制到新的支持缓冲区。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接