libaco: 一个极速的轻量级 C 非对称协程库 🚀 (10 ns/ctxsw + 一千万协程并发仅耗内存 2.8GB + Trending)

2018-07-17 20:25:28 +08:00
 hnes

项目链接:https://github.com/hnes/libaco

大家好,下面是这个项目的简要介绍:

上文中的"最快"指的是在满足 Sys V ABI Intel386 或者 AMD64 约束下最快的上下文切换实现。

中文文档正在做最后的 review,今晚或者明天发布,敬请期待 :D

下面是前几天在 Reddit 上讨论的帖子列表:

热烈欢迎大家的问题和建议 ;-)

Edit: 添加项目链接。

9090 次点击
所在节点    分享创造
62 条回复
hnes
2018-07-19 22:16:09 +08:00
@pkv 十分感谢你的支持 :D

实在很抱歉这么晚才回复你 :-) 两个问题我分开回复了哈。

> 有没有和过其它 C/C++ 协程库的对比结果,不只是自己跑的 benchmark,如对比微信使用的 libco

第一点,腾讯的 libco 的实现中有严重的 bug (堆上的内存会被随机性的破坏),对于对正确性要求很高的应用不推荐使用,否则程序可能在运行过程中做很多令人意想不到的任何事情。

第二点,即使是和实现错误的腾讯 libco 相比,libaco 的协程上下文切换汇编实现 acosw 的速度是腾讯 libco 的 coctx_swap 的 1.7 倍。libaco 的分支中有相关的性能对比报告和代码。

(实在抱歉,回复中不能加链接了,相关的链接可以在帖子最上方的附言中找到)
hnes
2018-07-19 22:32:31 +08:00
@pkv

> 有没有在生产环境使用?

有,但是用户量目前没有微信那么大。

尽管还没有像微信那样大量的用户来做生产中的 Beat 测试,但下面有一些你可能会觉得很有用的统计数据:

在 libco 的核心中只有不超过 700 行的代码,但是花了我一个多月的时间才编写完成它的第一个版本,还有完整的一个月时间来进行非常严格的代码审查(几乎每一行代码都被严格地证明和推理过,并且被审查证明过很多次),当然,还有全面的测试。libaco 是我们正在开发的产品中最重要的核心组件之一,因此我们对它投入了很大的精力和注意力。

还有一件事,有时来自巨量用户的 Beat 测试并不总是足够的,例如刚才我们提到的微信 libco 的案例,对于这种重要的短小精悍的基础设施软件,只有通过严格的代码审查、对系统的深刻理解和正确的设计才能做到 bug free,测试(包括生产中的应用测试)一般只能检测出很低级的错误。

我相信 libco 的生产使用会随着时间的推移而逐渐增加,而且,在 libaco 的刚刚发布的 20 天不到,已经有国外的公司开始准备在生产中使用它了。
hnes
2018-07-20 09:00:34 +08:00
@pkv 另外,关于 libaco 的速度问题,libaco 的 acosw 协程上下文切换汇编是在 Sys V ABI intel386 或 AMD64 的约束之下的最快且正确的实现,这是经过数学证明了的,具体可以阅读 libaco 文档中的数学证明部分。

感谢你的关注 ;-)
hnes
2018-07-20 09:23:44 +08:00
@hnes

勘误:

> 在 **libco** 的核心中只有不超过 700 行的代码,但是花了我一个多月的时间才编写完成它的第一个版本,还有完整的一个月时间来进行非常严格的代码审查(几乎每一行代码都被严格地证明和推理过,并且被审查证明过很多次),当然,还有全面的测试。libaco 是我们正在开发的产品中最重要的核心组件之一,因此我们对它投入了很大的精力和注意力。

**libco** -> libaco

十分抱歉...
pkv
2018-07-20 10:20:54 +08:00
@hnes 很棒的工作,赞!!!牛逼!!!
这么短时间内,有公司敢用,也是勇气可嘉。
数学证明很牛逼,在这里没多少人能看懂吧,即使是写论文,最终也得拿个数据说话。
Q:问个小白问题,我理解底层是基于 epoll 类似的事件机制,libaco 中实现的类似 Go 语言中 goroutine 调度器的角色,libaco 其实是降低用户实现高并发的难度,以类同步编程实现异步,用户友好,如果有能力做到类似 Nginx 这种高效的使用,应该没必要使用协程的对吗?
hnes
2018-07-20 16:56:19 +08:00
> 很棒的工作,赞!!!牛逼!!!

十分感谢你的鼓励 :D

> 这么短时间内,有公司敢用,也是勇气可嘉。
> 数学证明很牛逼,在这里没多少人能看懂吧

这个我有些不赞同哈,libaco 核心只有 700 行代码,如果对 ABI 规范和汇编有一定了解的程序员,第一次几乎半个小时不到就能够读完了,就算是对 ABI 与汇编有些生疏,只要温习一下这方面的知识一样可以轻松读懂。另外,数学证明其实是非常简单的,有不少朋友向我反馈他们只是看了一下文档中的几张示意图就完全明白了 libaco 的设计思想和原理。

程序某种程度上说就是数学问题,证明当然永远是第一位的。

> 即使是写论文,最终也得拿个数据说话

数据,指的是?文档中有详细的 benchmark 方法和结果报告。如果是指用户量的话,任何软件产品都是先发布,然后才能有用户量,不是么?

> Q:问个小白问题,我理解底层是基于 epoll 类似的事件机制,libaco 中实现的类似 Go 语言中 goroutine 调度器的角色,libaco 其实是降低用户实现高并发的难度,以类同步编程实现异步,用户友好

赞,正是如此 ;-)
下一个将要发布的开源项目正是完成了你所陈述的所有事情,热烈欢迎继续关注哈 :D

> 如果有能力做到类似 Nginx 这种高效的使用,应该没必要使用协程的对吗?

是这样的,如果喜欢使用 callback 方式写网络应用并且愿意付出相应的开发代价,当然是没有任何问题的啦,但是,有时候,有能力和有时间有精力完全是不同的,哈哈。
最后插一句:其实 Nginx 的网络模型和事件模型是非常简单的( proxy ),只要对比一下 ngx lua 和 golang 两者的调度器和编程抽象就可以看出来了。
Ganing
2018-07-21 09:07:01 +08:00
点赞。从 LT 到 ET 事件驱动,再加上多线程锁,真的写起来太麻烦了。正在学习 goroutine 的原理,协程还是很有启发的
hnes
2018-07-21 09:15:17 +08:00
@Ganing 十分感谢你的鼓励 :D

> 从 LT 到 ET 事件驱动,再加上多线程锁,真的写起来太麻烦了。正在学习 goroutine 的原理,协程还是很有启发的

是的,golang 中有很多很棒的思想可以在设计系统时让我们借鉴;但是,由于它的抽象层次过高,使得它不大适合某些对并发和资源消耗要求很高的领域,欢迎你继续关注 libaco 的后续开源项目哈 ;-)
pynix
2018-07-21 13:55:23 +08:00
手动调度,坑多啊。。。
hnes
2018-07-21 19:46:34 +08:00
@pynix 后续我会继续发布一个基于 libaco 的 Golang like C 实现,敬请继续关注哈 ;-)
Monad
2018-07-22 02:53:16 +08:00
@hnes #49 好奇 能否给一个 libco 破坏内存的最小复现例子?
hnes
2018-07-22 07:56:25 +08:00
你好 @Monad,Github 上腾讯 libco 的 issue 90 中有十分详细的复现过程以及标准规范的引述,另外我在知乎上的一个回答中还陈述了腾讯 libco C++ ABI 相关的问题,感兴趣的话可以看一下哈 :D

tencent libco issue 90: https://github.com/Tencent/libco/issues/90
zhihu 回答: https://www.zhihu.com/question/52193579/answer/447612082
yulon
2018-07-23 08:55:30 +08:00
LZ 你好,我问你个可能很 safufu 的问题🤣,我看有些协程的实现中会用 pushfq/popfq 和 fnstenv/fldenv,这和单用 fnstcw/fldcw 有什么区别呢,因为加上 pushfq/popfq 后性能甚至不如 Windows Fiber 就有感而问。
hnes
2018-07-23 10:32:37 +08:00
> LZ 你好,我问你个可能很 safufu 的问题🤣,我看有些协程的实现中会用 pushfq/popfq 和 fnstenv/fldenv,这和单用 fnstcw/fldcw 有什么区别呢,因为加上 pushfq/popfq 后性能甚至不如 Windows Fiber 就有感而问。

朋友客气了哈 :D

1. 关于 pushfq/popfq

> The direction flag DF in the %rFLAGS register must be clear (set to “ forward ” direction) on function entry and return. Other user flags have no specified role in the standard calling sequence and are not preserved across calls.

> Sys V ABI AMD64 Version 1.0

> The direction flag DF in the %EFLAGS register must be clear (set to “ forward ” direction) on function entry and return. Other user flags have no specified role in the standard calling sequence and are not preserved across calls.

> Intel386-psABI-1.1

Sys V ABI 规范中规定(E|R)FLAGS 中的 status flags 都是"not preserved across calls",所以当我们进行遵从 Sys V ABI 规范的协程上下文切换时,根本不需要 save/restore 它们(比如 libaco 的 acosw 实现);另外,尽管 ABI 对(E|R)FLAGS 中的 DF 位有约束,但是我们可以证明在我们的协程上下文切换汇编中,它也是可以省掉的,证明很巧妙,具体可以阅读 libaco 的数学证明部分。

2. 关于 fnstenv/fldenv

> The control bits of the MXCSR register are callee-saved (preserved across calls), while the status bits are caller-saved (not preserved). The x87 status word register is caller-saved, whereas the x87 control word is callee-saved.

> Intel386-psABI-1.1

> The control bits of the MXCSR register are callee-saved (preserved across calls), while the status bits are caller-saved (not preserved). The x87 status word register is caller-saved, whereas the x87 control word is callee-saved.

> Sys V ABI AMD64 Version 1.0

Sys V ABI 规范中规定对于 x87 与 MXCSR 只有 control words 是“ callee-saved (preserved across calls)”的,它们的 status 位全都是“ not preserved across calls ”,所以在 libaco 的协程上下文切换中,我们只需要 save/restore 它们的控制字就可以了( fnstcw/fldcw & stmxcsr/ldmxcsr ),当然使用 fnstenv/fldenv 也是正确的,但是:

fnstcw/fldcw 只需要 save/restore 两个字节,而 fnstenv/fldenv 却需要 save/restore 28 个字节,这个 28 个字节中有 26 个字节是毫无必要的无用功。

(对于上述两部分的 call convention,Sys V ABI 与 Miscrosoft ABI 是基本相同的,故上面只引述了 Sys V ABI 的规范描述)

Reference:
https://www.felixcloutier.com/x86/FSTENV:FNSTENV.html
https://www.felixcloutier.com/x86/FSTCW:FNSTCW.html
https://github.com/hnes/libaco/blob/master/README_zh.md#proof-of-correctness

> 加上 pushfq/popfq 后性能甚至不如 Windows Fiber

你是指如果没有 pushfq/popfq 的话,性能比 Windows Fiber 好很多,加上之后反而会比 Windows Fiber 慢很多么?
如果是上述陈述的话,我认为是很奇怪的,可以具体再确认一下,毕竟 pushfq/popfq 还是很快的( RFLAGS 8 个字节,但是还有可能是因为 pushfq/popfq 破坏了流水线而带来的性能损耗)。
但是如果本来性能与 Fiber 相等,加上它们之后更慢了,这就很正常的,毕竟这样就多了来回 16 个字节读写的无端损耗。

当然,我认为是不需要加的,除非还有其他的目的。

对于 windows 的协程,建议只用它提供的 API,比如 Fiber,原因是 windows 的 ABI 只有微软说了算...哈哈。
hnes
2018-07-23 10:46:54 +08:00
@yulon

> Sys V ABI 规范中规定(E|R)FLAGS 中的 status flags 都是"not preserved across calls",所以当我们进行遵从 Sys V ABI 规范的协程上下文切换时,根本不需要 save/restore 它们(比如 libaco 的 acosw 实现);另外,尽管 ABI 对(E|R)FLAGS 中的 DF 位有约束,但是我们可以证明在我们的协程上下文切换汇编中,它也是可以省掉的,证明很巧妙,具体可以阅读 libaco 的数学证明部分。

中更正:

Sys V ABI 规范中规定(E|R)FLAGS 中的 status flags {+(除了 DF 位)+}都是"not preserved across calls"
yulon
2018-07-23 20:01:17 +08:00
@hnes 原来如此,感谢解答!!!
yulon
2018-07-23 20:45:07 +08:00
@hnes 是我忽略了 fnstenv/fldenv 要占更多字节,其实主要是这货拖慢了效率,只加 pushfq/popfq 还是比 Fiber 快的🤣
hnes
2018-07-23 22:26:21 +08:00
@yulon 不客气哈,十分高兴上面的回复能够对你有用处 ;-)

也很感谢你的案例分享 :D
toilaj
2018-07-24 08:51:50 +08:00
已 fork,学习中~~
hnes
2018-07-24 09:04:44 +08:00
@toilaj 十分感谢你的支持哈,遇到任何问题都可以提出来大家一起讨论 :D

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/471755

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX