CgoGo 的一个非常重要的部分:

它是你调用任何任何非Go代码的窗口(或者更确切地说,任何由C语言绑定的东西)。

对于 CockroachDB 来说,

cgo 减轻了我们在处理连接 ROcksDB 的存储层(storage layer)时候的压力,

而 RocksDB 这个存储引擎目前在Go语言生态环境中没有良好的替代品,

至少在我们的任职范围内没有。

经过几次迭代之后,

我们发现处理这些外部库的正确方法——我们有相当多的外部库——是将它们处理后放在Go语言的依赖包(Go wrapper packages)中:

虽然Cgo为我们提供了便利,但是请远没有直接调用那么简单。

经验丰富的Cgo使用者(cgo-er)可能会知道这一点(并且可能更倾向于轻描淡写地在这篇文章的剩余部分上浏览),但是使用Cgo附带一些警告,我们将在下面讨论我们建议的解决方案。

调用的开销(Call Overhead)

调用 Cgo 带来的开销将比在Go语言内部的调用带来的开销大几个数量级。

这听起来很可怕,但实际上在很多应用中都不是问题。

然我们来观察一下这个 cgobench 包:

func BenchmarkCGO(b *testing.B) {
CallCgo(b.N) // call `C.(void f() {})` b.N times
} // BenchmarkGo must be called with `-gcflags -l` to avoid inlining.
func BenchmarkGo(b *testing.B) {
CallGo(b.N) // call `func() {}` b.N times
}
$ go test -bench . -gcflags '-l'    # disable inlining for fairness
BenchmarkCGO-8 10000000 171 ns/op
BenchmarkGo-8 2000000000 1.83 ns/op

换句话说,在这个(公认最小的)例子中,大约有100个因素参与其中。

大家不要因此疯狂。

按照绝对时间,171 ns通常是一个完全可以接受的成本,特别是如果你的C代码做了大量的工作。

然而,在我们的例子中,我们在一些测试中计时了数以万计的Cgo调用,

所以我们把一些代码推到C上以减少迭代次数。

我们的结论是调用开销并不重要,等价的C++和Go的实现在性能上是难以区分的。

然而,由于能够编写更高效的实现,我们仍然将一些操作移到C++上,并进行了fat改进

手动ISH内存管理(Manual-ish Memory Management)

GO是一门垃圾回收语言,但C不是。

这意味着从C传递数据到Go不应该粗心地进行,并且拷贝常常是不可避免的,反之亦然。

(译者的理解:C语言中的数据不会自动释放,而Go语言中的数据会自动进行垃圾回收,所以在两者之间传输数据的时候可能会碰到数据被提前释放,或者该释放但是没释放的问题)

尤其是(在我们经常)非常频繁地处理字节字符串和接口的时候。

传统的对 C.CString 及 C.GoBytes 的使用会大大增加内存压力,

当然,复制数据也会明显消耗CPU性能。

在一些场景下,我们有办法避免这种类型的拷贝。

举个例子,在便利键的时候,我们使用如下办法

func (r *rocksDBIterator) Key() []byte {
return C.GoBytes(unsafe.Pointer(r.key), s.len)
} func (r *rocksDBIterator) Next() {
// The memory referenced by r.key stays valid until the next operation
// on the iterator.
r.key = C.DBNext(r.iter) // cgo call
}

如果我们想要做的是检查一个标准的当前键,我们知道底层内存在我们需要的时候是不会被释放掉的。

因此,这个(编写的)代码似乎是浪费的:

for ; iter.Valid(); iter.Next() {
if bytes.HasPrefix(iter.Key(), someKey) { // copy!
// ...
}
}

为了减轻所有这些拷贝的成本,我们添加(并使用)函数 Key() 的零拷贝(和不安全)版本:

// unsafeKey() returns the current key referenced by the iterator. The memory
// is invalid after the next operation on the iterator.
func (r *rocksDBIterator) unsafeKey() []byte {
// Go limits arrays to a length that will fit in a (signed) 32-bit
// integer. Fall back to copying if our slice is larger.
const maxLen = 0x7fffffff
if s.len > maxLen {
return C.GoBytes(unsafe.Pointer(r.key), s.len)
}
return (*[maxLen]byte)(unsafe.Pointer(s.data))[:s.len:s.len]
}

这看上去是一个更高效和安全的办法,但是在正确使用的时候,它涉及到更多需要考虑的地方。

我们正在创建一个由C分配的内存支持的切片。

我们需要小心,而我们的切片(或任何衍生切片)仍在使用中,而与此同时C内存没有释放。

(译者的理解:C内存没有释放,对切片的改动,不能引起内存的变化,那我们度数据是从内存中读的,

读到的还是原来的数据)

我们可以解决这个问题,因为这个问题出现在我们的底层代码中,但这肯定不是任何类型的面向公众的API的选择;

这将保证一些用户不会遵守对返回的字节切片的微妙契约,并且随机地体验空指针异常。

Cgoroutines != Goroutines

这可能是一个严重的问题,而当你想到它的时候是显而易见的,当你不这样做的时候,它会是一个惊喜。

考虑如下代码:

func main() {
for i := 0; i < 1000; i++ {
go func() {
time.Sleep(time.Second)
}()
}
time.Sleep(2*time.Second)
}

这个无聊的程序不会有多大效果。1000个 gouroutines 几乎免费地来了,分配给它们的“堆栈”只有几千字节。

如果我们把 cgo 带入这个编码游戏呢?

下面的代码是 cgobench 中一个示例的简化版本:

//#include <unistd.h>
import "C" func main() {
for i := 0; i < 1000; i++ {
go func() {
C.sleep(1 /* seconds */)
}()
}
time.Sleep(2*time.Second)
}

“惊喜”是这段代码的效果(和上面那段)非常不同。

一个阻塞式的 cgo 调用会占用一个系统线程;

Go运行时库 不能像 goroutine 那样调度它们,而堆栈是一个真正的堆栈,其数量是兆字节的!

同样,如果你调用适当的有界并发的 cgo ,那就没什么大不了的。

但是如果你写的话,你可能已经习惯于不太考虑 Goroutines 了。

在关键请求路径中的阻塞cgo调用可能会给你带来成百上千的线程,

这些线程可能会 导致问题

特别地,ulimit -rdebug.SetMaxThreads 可能会导致程序地快速终止。

或者,按照 Dave Cheney 地话来说,

  • “过多的 cgo 使用中断 Go 轻量级并发的承诺。”

交叉编译(Cross Out Cross-Compilation)

使用 cgo ,你会丢失(或者更确切地说,你不会很好地体验到)交叉编译在 Go 1.5 或更高把呢不能中工作的易用性。

这并不奇怪(因为交叉编译与C依赖关系必然需要交叉编译C依赖),但是如果您在和Go自己的包或外部库之间进行选择,那么这可能是一个标准。

Dave Cheney在此处发表的一篇文章 通常是关于可用信息的最佳来源。

静态构建(Static Builds)

这种情况和交叉编译的情况类似,不过和交叉编译相比情况较好一点。

使用 cgo 构建静态二进制文件仍然是可能的,但需要进行一些调整。

在Go 1.5之前,最突出的例子是必须使用 netgo 构建标签来避免在 glibc 中链接DNS解析。

这已成为默认的处理方式,

但仍然存在一些微妙之处,

例如必须指定自定义 -installsuffix(以避免使用非静态生成中的缓存生成)、

将正确的标志传递给外部链接器(external linker,在本例中,-extldflags “-static”),

并用 -a 构建以执行一个完全的重建。

不是所有的这些都是必要的,但是你需要明白这一点:

它变得更加手动,并且所有的重建速度都变慢了。

对于所有感兴趣的人来说,

这是我与 cgo 的第一次约会(以及后来的)角斗 和

一个神秘的错误

我们可以在未来的帖子中再次找到。

调试(Debugging)

调试你的代码会更困难(在使用 cgo 的情况下)。

驻留在C中的部分代码不容易通过Go的工具访问。

PProf、运行时统计(runtime statistics)、行号(line numbers)、

堆栈跟踪(stack traces) —— 当你越过边界时,所有的东西都会减弱。

GoRename 及其朋友可能 偶尔会将源代码中的标识符丢弃

而这些标识符(原本应该)在转换后将转换为 cgo 生成的代码。

由于这些工具通常运行的很好,所以出现的一些损失总会让人感到不安。

但是,gdb仍然有效地工作着。

总结(Summary)

总而言之,cgo 是一个具有局限性的伟大工具。

我们最近开始把一些低级操作转移到C++,这给了一些令人印象深刻的加速

其他的尝试并没有带来更多地性能提升。

性能地表现不是还令人满意吗?

CockroachDB学习笔记——[译]Cgo的成本与复杂性的更多相关文章

  1. CockroachDB学习笔记——[译]CockroachDB中的SQL:映射表中数据到键值存储

    CockroachDB学习笔记--[译]CockroachDB中的SQL:映射表中数据到键值存储 原文标题:SQL in CockroachDB: Mapping Table Data to Key- ...

  2. CockroachDB学习笔记——[译]如何优化Go语言中的垃圾回收

    原文链接:https://www.cockroachlabs.com/blog/how-to-optimize-garbage-collection-in-go/ 原作者:Jessica Edward ...

  3. CockroachDB学习笔记——[译]在CockroachDB中如何让在线模式更改成为可能

    原文链接:https://www.cockroachlabs.com/blog/how-online-schema-changes-are-possible-in-cockroachdb/ 原作者: ...

  4. CockroachDB学习笔记——[译]为什么Go语言是CockroachDB的正确选择

    原文链接:https://www.cockroachlabs.com/blog/why-go-was-the-right-choice-for-cockroachdb/ 原作者:Jessica Edw ...

  5. CockroachDB学习笔记——[译]The New Stack:遇见CockroachDB,一个弹性SQL数据库

    原文链接:https://www.cockroachlabs.com/blog/the-new-stack-meet-cockroachdb-the-resilient-sql-database/ 原 ...

  6. CockroachDB学习笔记——[译]CockroachDB是如何进行分布式原子事务的

    原文:How CockroachDB Does Distributed, Atomic Transactions 原文链接:https://www.cockroachlabs.com/blog/how ...

  7. CockroachDB学习笔记——[译]Scaling Raft

    原文链接:https://www.cockroachlabs.com/blog/scaling-raft/ 原作者:Ben Darnell 原文日期:Jun 11, 2015 译:zifeiy 在Co ...

  8. CockroachDB学习笔记——[译]Hello World

    原文链接:https://www.cockroachlabs.com/blog/hello-world/ 原作者:Spencer Kimball 原文日期:Jun 4, 2015 译:zifeiy 数 ...

  9. CockroachDB学习笔记——对此的选择

    无意间了解到TiDB,然后知道了他是一款国产团队开源的NewSQL数据库, 看了一下官网,有很多中文的文档和技术分享挺不错的. 但是安装起来好像挺麻烦的说. 测试的硬件环境 也吓死我了,我只有一台笔记 ...

随机推荐

  1. 【二进制优化-多重背包】zznu-oj-2120 : 安详--如何用尽钱币打赏主播获得最大好感度

    2120 : 安详 题目描述 spring最近喜欢上了B站新秀主播,身为顿顿吃黄焖鸡的土豪,当然要过去打赏一番,但是spring还是喜欢精打细算,所以在打赏的时候,想要掏出有限的钱,获得主播的最大好感 ...

  2. java应用本地缓存

            在java应用中,对于访问频率比较高,又不怎么变化的数据,常用的解决方案是把这些数据加入缓存.相比DB,缓存的读取效率快好不少.java应用缓存一般分两种,一是进程内缓存,就是使用ja ...

  3. 《流畅的Python》Data Structures--第3章 dict 和 set

    dict and set 字典数据活跃在所有的python程序背后,即使你的源码里并没有直接使用它. 和dict有关的内置函数在模块builtins的__dict__内. >>> _ ...

  4. 从运行时的工作空间获取EMF文件(IFILE)

    //EMFFILE_URI为EMF文件的URI String uriString = EMFFILE_URI.trimFragment().toPlatformString(true); if (ur ...

  5. iar8.32版本关于cmsis的说明

    平台是cubemx5.3 keil5.26 带freertos,使用iar8.32,在上图中的use cmsis 打勾与否都能编译通过.

  6. Java8-Lock-No.02

    import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util ...

  7. Codeforces Educational Codeforces Round 54 题解

    题目链接:https://codeforc.es/contest/1076 A. Minimizing the String 题意:给出一个字符串,最多删掉一个字母,输出操作后字典序最小的字符串. 题 ...

  8. BZOJ 4129 Haruna’s Breakfast ( 树上带修莫队 )

    题面 求树上某路径上最小的没出现过的权值,有单点修改 添加链接描述 分析 树上带修莫队板题,问题是怎么求最小的没出现过的权值. 因为只有nnn个点,所以没出现过的最小值一定在[0,n][0,n][0, ...

  9. Sort Letters by Case

    Given a string which contains only letters. Sort it by lower case first and upper case second. Examp ...

  10. Educational Codeforces Round 39 (Rated for Div. 2) 946E E. Largest Beautiful Number

    题: OvO http://codeforces.com/contest/946/problem/E CF 946E 解: 记读入串为 s ,答案串为 ans,记读入串长度为 len,下标从 1 开始 ...