V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
hnes
V2EX  ›  分享创造

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

  •  5
     
  •   hnes · 2018-07-17 20:25:28 +08:00 · 9045 次点击
    这是一个创建于 2319 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    • 除了一个生产级别的 C 协程库实现,还包含了一个详细的文档描述了如何实现一个 最快且正确 的协程库以及其严格的数学证明;
    • 核心实现不超过 700 行代码,包含了一个协程库应该有的全部功能;
    • 在 AWS c5d.large 机器上的性能测试结果指出,一次协程间上下文切换仅耗时 10 ns (独立执行栈);
    • 用户在创建新的协程时,可以选择其拥有一个独占的执行栈,或者是与其它任意数量的协程一起共享一个执行栈;
    • 拥有极致的内存使用效率:一千万个协程并发执行仅消耗 2.8GB 的物理内存( tcmalloc, 每一个协程使用 120B 的复制栈)。

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

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

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

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

    Edit: 添加项目链接。

    第 1 条附言  ·  2018-07-18 21:39:46 +08:00

    Edit:

    中文文档已经到位,欢迎诸位朋友阅读 :D

    链接: https://github.com/hnes/libaco/blob/master/README_zh.md.

    第 2 条附言  ·  2018-07-19 22:15:55 +08:00
    @pkv 抱歉,回复中不能加链接了...

    腾讯 libco Bug Issue: https://github.com/Tencent/libco/issues/90

    和腾讯 libco 性能测试对比分支: https://github.com/hnes/libaco/tree/tencent_libco_bug_report_and_coctx_swap_benchmark
    62 条回复    2018-09-09 22:14:26 +08:00
    lekai63
        1
    lekai63  
       2018-07-17 20:28:34 +08:00 via iPhone   ❤️ 1
    沙发。看起来很吊的样子
    hnes
        2
    hnes  
    OP
       2018-07-17 20:31:26 +08:00
    @lekai63 感谢你的支持 :D
    byteli
        3
    byteli  
       2018-07-17 20:39:43 +08:00 via Android   ❤️ 1
    板凳,支持一下,收藏了有空看看。文档看起来非常优秀
    hnes
        4
    hnes  
    OP
       2018-07-17 20:43:10 +08:00
    @byteli 十分感谢 :D 文档确实是花了很大的心血,希望能对使用和研究协程的朋友们有用处 ;-)
    Kilerd
        5
    Kilerd  
       2018-07-17 21:43:53 +08:00   ❤️ 1
    6666. mark 最近刚好想研究一下协程在底层的具体实现。有空读下代码。
    hnes
        6
    hnes  
    OP
       2018-07-17 21:51:06 +08:00
    @Kilerd 十分感谢哈 ;-)
    协程的实现以及应用确实非常有趣,希望这个项目的代码以及文档能够对你有用处,如果遇到任何问题可以发 Issue 大家一起讨论 😁
    icylord
        7
    icylord  
       2018-07-17 22:47:15 +08:00   ❤️ 1
    厉害,协程一直都处于看不懂的状态
    hnes
        8
    hnes  
    OP
       2018-07-17 23:23:24 +08:00
    @icylord 感谢 :D

    协程的根本思想应该是比较清晰的。

    但如果要具体地去实现一个协程库,需要对系统的 ABI 规范很熟悉(重点是二进制函数调用中寄存器与栈的一些约定细节),也需要些汇编(重点是函数调用约定部分)的基础知识(这部分其实比较简单,如果对计算机体系结构比较熟悉的话初学它可能一两个小时就学会了)。

    比如腾讯的 libco 中就有一个[bug]( https://github.com/Tencent/libco/issues/90),在微信的生产系统中隐藏了五年多都没有被发现(从它的 Github 代码仓库中看是如此),就是因为在实现的时候没有严格遵守 Sys V ABI 的规范导致的。

    libaco 项目中的文档还是比较齐全的,而且还有严格的数学证明,希望对你有所助益哈 ;-)

    当然,如果有问题的话,可以发 Issue 大家一起探讨,这样会更加有乐趣 :D
    hnes
        9
    hnes  
    OP
       2018-07-17 23:25:17 +08:00
    实在抱歉,纠正:

    链接: https://github.com/Tencent/libco/issues/90

    > 比如腾讯的 libco 中就有一个 bug,在微信的生产系统中隐藏了五年多都没有被发现(从它的 Github 代码仓库中看是如此),就是因为在实现的时候没有严格遵守 Sys V ABI 的规范导致的。
    lsmgeb89
        10
    lsmgeb89  
       2018-07-17 23:29:51 +08:00   ❤️ 1
    有证明厉害,看看
    tommydong
        11
    tommydong  
       2018-07-17 23:39:16 +08:00 via iPhone   ❤️ 1
    好奇的问句 1000 万的协程并发的应用场景?
    tommydong
        12
    tommydong  
       2018-07-17 23:39:54 +08:00 via iPhone   ❤️ 1
    还是很厉害👍 要赞一个
    jedihy
        13
    jedihy  
       2018-07-18 01:20:18 +08:00   ❤️ 1
    厉害
    est
        14
    est  
       2018-07-18 01:45:51 +08:00 via Android   ❤️ 1
    厉害
    hnes
        15
    hnes  
    OP
       2018-07-18 08:36:34 +08:00
    @tommydong 早上好,实在不好意思哈,昨天回复完上一位朋友的留言后就去睡觉了 ;-)

    > 1000 万的协程并发的应用场景?

    一个简短的总结就是,高性能、高并发和对内存使用效率要求很高的场景(当然,对这三点要求低些的应用肯定都是能用的,哈哈)。

    一个最典型的案例就是网络服务程序,比如大型集群的前端调度器(单机并发数百万数量级,比如像以用户空间 dpdk 实现的 Director ),以及像 Nginx、Redis 这样的对以上三点要求很高的网络服务基础设施。

    穿插一些相关的:

    Golang 也很棒,但是却不太适合上面的场景,与 C/C++相比,Golang 的编译器的优化有较大的提升空间(且 GCC 已经有 30 多年的演进历史),语言的抽象层过于重量级( Golang 的核心开发小组为了用户的易于使用已经牺牲了太多的性能,在多线程高并发的场景下,调度器损耗太大,当然还有 GC 问题...)。

    我其实也是 Golang 的长期用户和爱好者,如果碰到对这些指标要求不是很高的服务程序我还是会很乐意选择 Golang 的 ;-)
    hnes
        16
    hnes  
    OP
       2018-07-18 08:40:16 +08:00
    @lsmgeb89 感谢 :D 我也非常喜欢数学证明,写的时候也非常地开心。
    hnes
        17
    hnes  
    OP
       2018-07-18 08:49:45 +08:00
    @jedihy @est 十分感谢 :D 希望这个项目能够对朋友们有些用处 ;-)
    pymumu
        18
    pymumu  
       2018-07-18 08:50:35 +08:00 via Android   ❤️ 1
    这个轮子对比 libc 的 ucontext 协程有什么优势?
    missdeer
        19
    missdeer  
       2018-07-18 09:03:45 +08:00   ❤️ 1
    支持什么系统?什么编译器?
    hnes
        20
    hnes  
    OP
       2018-07-18 09:07:33 +08:00
    @pymumu

    libaco 的上下文切换`acosw`耗时为 10ns (RHEL-7.5 & c5d.large on AWS),而 ucontext 中有关于 sigprocmask 的系统调用(而且对于应用来说,在绝大多数情况下这个 Syscall 是没有存在必要的),时间数量级在 200-300ns 之间,而且系统调用会给当前运行线程带来严重的 cache-miss,会进一步影响线程的运行,所以,真正注重性能的网络应用是不会选择使用 ucontext (还有一点,ucontext 中的 setcontext 等等函数已经从 POSIX 标准中移除)。

    PS:

    $ man 3 setcontext
    ...
    CONFORMING TO
    SUSv2, POSIX.1-2001. POSIX.1-2008 removes the specification of getcontext(), citing portability issues, and recommending that applications be rewritten to use POSIX threads instead.
    ...

    另外,OpenBSD 中并没有实现 ucontext。
    hnes
        21
    hnes  
    OP
       2018-07-18 09:11:14 +08:00
    @pymumu 另外,10ns 在 3.5GHz 的 CPU 上大概只有 35 个时钟周期,这仅仅约为一个 32 位整型除法指令的耗时。


    Intel Haswell:

    instruction latency (core clock cycles)
    add 1
    mul 3
    div r32 22-29
    div r64 32-96
    hnes
        22
    hnes  
    OP
       2018-07-18 09:21:18 +08:00
    @missdeer 目前支持所有 ABI 规范为 Sys V ABI Intel386 以及 AMD64 的操作系统,包括所有 Unix 系的操作系统,比如 Linux, BSD, MacOS, Minix, SunOS...等等。
    具体只要查询一下目标系统的 ABI 规范是否为 Sys V ABI Intel386 或 AMD64 即可 ;-)
    hnes
        23
    hnes  
    OP
       2018-07-18 09:24:12 +08:00
    @missdeer 编译器的话目前已知可以用的有 Gcc 和 Clang,正常情况下编译工具链只要包含 C 编译器和汇编器(还有链接器)应该都没有问题,当然具体的可以尝试一下哈。
    hnes
        24
    hnes  
    OP
       2018-07-18 09:25:59 +08:00
    @missdeer 后面也会加入对 ARM 的支持,以及对 Windows 操作系统的支持( Microsoft ABI ),敬请关注 :D
    pymumu
        25
    pymumu  
       2018-07-18 09:33:37 +08:00   ❤️ 1
    @hnes 你总结的没错,ucontext 上下文切换是相对比较耗时的。
    posix 移除此接口原因是 makecontext 函数与 ISO C 不兼容,这个两个有差别吗?

    c 的协程对编程的要求比较高,如果切换不合理,一不小心性能反而会下降,并且协程如果调用系统调用阻塞了,那整个任务也就挂住了。并且现在多核的情况下,协程并不能有效利用 CPU。

    协程的本意是用户态调度,目的是让写代码的人,感知不到切换,也就是一个上下文做一件事情,没有状态,这是对比异步 IO+多路复用来说的。
    但我觉得对于高并发程序,还是应该使用异步 IO+多路复用的模式来写,因为底层 API 对异步 IO 已经支持很好了。支持几十万,上百万处理不是问题。

    go routine 在这方面其实优化的很好,不仅保证了写代码的便捷,同时保证了性能。

    如果这个库能让 C 有类似的能力的话,就很好了。否则也只是特定的业务使用,并且还要小心使用。
    tt67wq
        26
    tt67wq  
       2018-07-18 09:34:27 +08:00   ❤️ 1
    碉堡了!
    feverzsj
        27
    feverzsj  
       2018-07-18 09:34:51 +08:00   ❤️ 1
    可以和 vc 现在支持的 c++ stackless coroutine 做下性能比较
    hnes
        28
    hnes  
    OP
       2018-07-18 09:51:24 +08:00
    @pymumu 感谢你的建议 :D

    > c 的协程对编程的要求比较高,如果切换不合理,一不小心性能反而会下降,并且协程如果调用系统调用阻塞了,那整个任务也就挂住了。

    是这样的,但是协程的使用目标一般都是借此实现类似 ngx lua cosocket 的这种编程模型的(用同步的语义书写异步程序,彻底摆脱所谓的 callback hell ),后面我还会继续放出来一个满足这个要求的库,可以认为是一个 C 语言实现的“ Golang ”定制版(砍掉 golang 中不适合高性能高并发低消耗场景的一些东西,尤其是复杂重量级的调度器和 GC 逻辑) :D

    ngx lua cosocket: https://moonbingbing.gitbooks.io/openresty-best-practices/ngx_lua/whats_cosocket.html

    > 并且现在多核的情况下,协程并不能有效利用 CPU

    但是,为什么不能是多线程+协程(协程数 : 线程数 == C*T : T )呢?

    > 如果这个库能让 C 有类似的能力的话,就很好了。否则也只是特定的业务使用,并且还要小心使用。

    是的,后面将会放出一个这样的库,完全满足你的设想 :D

    (之所以打算两个分开放出来,是因为我认为协程库是很多场景中都有很大用处的一个组件,分开放出来应该对使用者们更有利,可以自由的定制选择)
    hnes
        29
    hnes  
    OP
       2018-07-18 09:52:20 +08:00
    @tt67wq 感谢支持哈 ;-)
    tommydong
        30
    tommydong  
       2018-07-18 09:56:43 +08:00 via iPhone   ❤️ 1
    @hnes 这种情况在 iass 的 LB 服务里面的确需要 ,我们以前就是基于 dpdk 做的。dpdk 是绕过了 linux 的内核协议栈,这个库不知道有没有做这方面的工作
    hnes
        31
    hnes  
    OP
       2018-07-18 09:57:30 +08:00
    @feverzsj 感谢你的提议,有机会一定尝试测试下 :D
    hnes
        32
    hnes  
    OP
       2018-07-18 10:04:24 +08:00
    @tommydong 之前研究过集群前端调度器很长时间,不过现在我没有在从事这方面的工作。

    但是 libaco 是通用的,自然可以自由的与 dpdk 结合在一起,后续我还会放出来一个类 Golang 的 C 多线程网络库(前面给 pymumu 的回复中有具体的描述),只要修改一下底层 socket 的 API,应该就能直接的用在 dpdk 上面了。
    hnes
        33
    hnes  
    OP
       2018-07-18 10:07:01 +08:00
    @tommydong 当然如果是要做 tcp/udp 层的编程,还要解决好用户空间协议栈的问题 :D
    deadEgg
        34
    deadEgg  
       2018-07-18 19:02:57 +08:00   ❤️ 1
    支持 ,以前被 GIL 搞的要死才用协程。

    请教楼主现在做这个库是为了解决什么场景的问题呢。还是说去解决一些性能上的问题呢?
    hnes
        35
    hnes  
    OP
       2018-07-18 20:40:38 +08:00   ❤️ 1
    @deadEgg 非常感谢你的支持 ;-)

    > 现在做这个库是为了解决什么场景的问题呢。还是说去解决一些性能上的问题呢?

    是的,主要是性能上的问题,但是,也可以认为是某种场景的解决方案。

    场景:

    高性能(应该接近极致)、高并发(百万数量级)和对内存使用效率(应该接近极致)要求很高的应用。

    我们要实现一个核心的网络服务,最初考察使用 go 来完成,后来经过了一段时间的考察实验,甚至已经写了很多的 go 代码,发现完全满足不了我们需求( GC、多线程切换损耗、内存消耗、调度器相关的等等相关的问题),最后还是决定选择使用 C 来实现。

    而且在网络编程时又不想过度的使用 callback 去手写状态机,所以最好的方法就是实现一个“用 C 定制的 golang ”,去掉 golang 对高性能高并发应用场景不利的的东西,在 C 的极速与 golang 的易用之间找到一个最适合我们应用场景的平衡点,而 libaco 就是这个项目中的核心组件--协程库的实现,后续将会把全部的实现代码都放出来,敬请期待 :D

    PS:

    我也是 go 的爱好者,如果遇到要求不是这么高的场景,还是非常乐意选择 go 的。
    wtcoder
        36
    wtcoder  
       2018-07-19 02:44:07 +08:00   ❤️ 1
    楼主牛人!小公司表示看了您作品的介绍,才发现~ 我们最大的性能瓶颈是带(Que)宽(Qian)...
    hnes
        37
    hnes  
    OP
       2018-07-19 08:00:28 +08:00
    @wtcoder 十分感谢你的鼓励 ;-)

    libaco 是开源的,热烈欢迎贵公司在生产环境中使用它,中间如果遇到任何的问题都可以发 Issue 大家一起来探讨哈 :D
    yulon
        38
    yulon  
       2018-07-19 16:01:38 +08:00   ❤️ 1
    libco 里面居然还有这种操作,我被秀到了_🤣」∠)_
    hnes
        39
    hnes  
    OP
       2018-07-19 16:49:34 +08:00
    @yulon 刚刚看到时确实比较惊讶,后来想了下,可能是代码贡献者当时想要省掉手写寄存器偏移地址的这个工作吧...
    pkv
        40
    pkv  
       2018-07-19 17:45:52 +08:00   ❤️ 1
    有两个小问题:
    1. 有没有在生产环境使用?
    2. 有没有和过其它 C/C++ 协程库的对比结果,不只是自己跑的 benchmark,如对比微信使用的 https://github.com/Tencent/libco

    谢谢~
    hnes
        41
    hnes  
    OP
       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
        42
    hnes  
    OP
       2018-07-19 22:32:31 +08:00   ❤️ 1
    @pkv

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

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

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

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

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

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

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

    勘误:

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

    **libco** -> libaco

    十分抱歉...
    pkv
        45
    pkv  
       2018-07-20 10:20:54 +08:00   ❤️ 1
    @hnes 很棒的工作,赞!!!牛逼!!!
    这么短时间内,有公司敢用,也是勇气可嘉。
    数学证明很牛逼,在这里没多少人能看懂吧,即使是写论文,最终也得拿个数据说话。
    Q:问个小白问题,我理解底层是基于 epoll 类似的事件机制,libaco 中实现的类似 Go 语言中 goroutine 调度器的角色,libaco 其实是降低用户实现高并发的难度,以类同步编程实现异步,用户友好,如果有能力做到类似 Nginx 这种高效的使用,应该没必要使用协程的对吗?
    hnes
        46
    hnes  
    OP
       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
        47
    Ganing  
       2018-07-21 09:07:01 +08:00 via Android   ❤️ 1
    点赞。从 LT 到 ET 事件驱动,再加上多线程锁,真的写起来太麻烦了。正在学习 goroutine 的原理,协程还是很有启发的
    hnes
        48
    hnes  
    OP
       2018-07-21 09:15:17 +08:00
    @Ganing 十分感谢你的鼓励 :D

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

    是的,golang 中有很多很棒的思想可以在设计系统时让我们借鉴;但是,由于它的抽象层次过高,使得它不大适合某些对并发和资源消耗要求很高的领域,欢迎你继续关注 libaco 的后续开源项目哈 ;-)
    pynix
        49
    pynix  
       2018-07-21 13:55:23 +08:00   ❤️ 1
    手动调度,坑多啊。。。
    hnes
        50
    hnes  
    OP
       2018-07-21 19:46:34 +08:00
    @pynix 后续我会继续发布一个基于 libaco 的 Golang like C 实现,敬请继续关注哈 ;-)
    Monad
        51
    Monad  
       2018-07-22 02:53:16 +08:00   ❤️ 1
    @hnes #49 好奇 能否给一个 libco 破坏内存的最小复现例子?
    hnes
        52
    hnes  
    OP
       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
        53
    yulon  
       2018-07-23 08:55:30 +08:00   ❤️ 1
    LZ 你好,我问你个可能很 safufu 的问题🤣,我看有些协程的实现中会用 pushfq/popfq 和 fnstenv/fldenv,这和单用 fnstcw/fldcw 有什么区别呢,因为加上 pushfq/popfq 后性能甚至不如 Windows Fiber 就有感而问。
    hnes
        54
    hnes  
    OP
       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
        55
    hnes  
    OP
       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
        56
    yulon  
       2018-07-23 20:01:17 +08:00   ❤️ 1
    @hnes 原来如此,感谢解答!!!
    yulon
        57
    yulon  
       2018-07-23 20:45:07 +08:00   ❤️ 1
    @hnes 是我忽略了 fnstenv/fldenv 要占更多字节,其实主要是这货拖慢了效率,只加 pushfq/popfq 还是比 Fiber 快的🤣
    hnes
        58
    hnes  
    OP
       2018-07-23 22:26:21 +08:00
    @yulon 不客气哈,十分高兴上面的回复能够对你有用处 ;-)

    也很感谢你的案例分享 :D
    toilaj
        59
    toilaj  
       2018-07-24 08:51:50 +08:00   ❤️ 1
    已 fork,学习中~~
    hnes
        60
    hnes  
    OP
       2018-07-24 09:04:44 +08:00
    @toilaj 十分感谢你的支持哈,遇到任何问题都可以提出来大家一起讨论 :D
    myself659
        61
    myself659  
       2018-07-31 14:39:29 +08:00
    文档很详细 下了很大功夫 赞
    HeartJ
        62
    HeartJ  
       2018-09-09 22:14:26 +08:00
    最近在学习协程,感谢分享和提供这么好的学习资源
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2792 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 11:51 · PVG 19:51 · LAX 03:51 · JFK 06:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.