前言

最近开的坑有点多。有点忙不过来了所以好久没写Blog了。这个C++20的协程接入一直在改造计划中,但是一直没抽出时间来正式实施。 在之前,我写过一个初版的C++20协程接入 《libcopp接入C++20 Coroutine和一些过渡期的设计》 。当时主要是考虑到 Rust也有和C++类似的历史包袱问题,所以参考了一些Rust协程改造过程中的设计。 但是后来尝试在项目中使用的时候发现还是有一些问题。首先C++20的协程并不是零开销抽象,所以强行用Rust的模式反而带来了一定开销和理解上的难度。其次原先的设计中 generator 是按类型去实现外部接入的。但是实际接入SDK的过程中我们有相当一部分类型相同但是接入流程不同的情况,再加上现在各大编译器也都已经让C++20协程的特性脱离 experimental 阶段了,有一些细节有所变化。所以干脆根据我们实际的使用场景,重新设计了下组织结构。

前言

最近GCC 10.1.0 发布,三大编译器(MSVC、GCC、Clang)都已经支持了C++20协程,之前给 libcopp 接入 C++20协程 的计划也就提上了日程。C++20协程 在创建、切换开销和内存分配上和传统有栈协程相比有着无可比拟的优势。但是C++20全面普及还有相当长一段时间,所以我们设计的重要目标之一就是能够让以后的迁移更容易且更平滑地进行,本文则是记录了 libcopp 接入 C++20协程 时地一些性能上和平滑接入上的思考和成果。

前段时间有同事联系我想看看可能推广我之前写的协程库 libcopp,虽然 libcopp 已经用到过好几个项目上,这几年也断断续续地写了一些实现细节的文章,但是也但确实需要系统、概览性地介绍下 libcopp ,所以就有了这篇文章。

前言

之前写了 《协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比》《C++20 Coroutine》 ,但是一直没写 C++20 Coroutine 的测试报告。

现在的草案版本比我当时写 《C++20 Coroutine》 的时候有了一点点更新,cppreference 上有文档了(https://en.cppreference.com/w/cpp/language/coroutines) 。里面列举的标准文档是P0912R5,这个文档目前还没完工,详情可以看他的来源N4775。不过内容上暂时还没有太大的变化,今天我就照着之前的方式来benchmark一波 C++20 Coroutine 吧。

最近抽空继续对 libcopp 进行了更新和小幅优化。 首先的Merge了 boost.context 1.70.0 。这次boost.context的更新似乎和它写进 CHANGELOG 里的并不完全一致,匹配的只看到 macho 架构的脏数据操作。 不过另外它增加了新的平台支持 mips64,我目前还是简单导入了,但是平台检测工具还没有写,如果要使用是可以通过编译参数切过去的,不过我感觉没人会这么用吧?我自己用都得看一下之前怎么写的。

协程系统优化

libcopp很早就实现完成了v2版本,现在迁移进atsf4g-co/tree/sample_solution以后也把v2分支正式并入了主干。原来的版本切出到v1分支并且停止维护了。

libcopp v2内存布局

开发libcopp v2版本的最大目的是优化allocator的接口和内存碎片。

原来的allocator虽然是可定制的,但是是内置的。每次创建一个allocator对象,不同allocator之间共享数据只能通过全局数据或者TLS数据。现在则可以传入allocator了。这也是为后续的共享栈池做准备。

之前测出来libcopp还有一些列优化点,但是要破坏之前的API,所以整理了一下优化的想法和方案。

预留空间和合并分配

之前有太多的堆内存分配了,导致很多碎片。那么第一个想法就是协程对象可以分配在栈上,runner也可以分配在栈上。然后还可以加一个自定义预留长度。每个对象对齐到sizeof(long),总长度对齐到64 Bytes。

本来是没想写这个对比。无奈之前和call_in_stack的作者聊了一阵,发现了一些libcopp的改进空间。然后顺便看了新的boost.context的cc部分的代码,有所启发。想给libcopp做一些优化,主要集中在减少分配次数从而减少内存碎片;在支持的编译器里有些地方用右值引用来减少不必要的拷贝;减少原子操作和减少L1cache miss几个方面。

之后改造了茫茫多流程和接口后出了v2版本,虽然没完全优化完,但是组织结构已经定型了,可以用来做压力测试。为了以后方便顺便还把cppcheck和clang-analyzer的静态分析工具写进了dev脚本。然后万万没想到的是,在大量协程的情况下,benchmark的结果性能居然比原来还下降了大约1/3。