OWenT's blog
  • Introduction
  • About Me
  • 2020
    • 近期对libatapp的一些优化调整(增加服务发现和连接管理,支持yaml等)
    • xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等)
    • 在游戏服务器中使用分布式事务
    • libcopp接入C++20 Coroutine和一些过渡期的设计
    • libatbus 的大幅优化
    • nftables初体验
    • 容器配置开发环境小计
  • 2019
    • PALM Tree - 适合多核并发架构的B+树 - 论文阅读小记
    • 跨平台协程库 - libcopp 简介
    • C++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比)
    • 尝鲜Github Action
    • 一些xresloader(转表工具)的改进
    • protobuf、flatbuffer、msgpack 针对小数据包的简单对比
    • 协程框架(libcopp) 小幅优化
    • Excel转表工具(xresloader) 增加protobuf插件功能和集成 UnrealEngine 支持
    • Anna(支持任意扩展和超高性能的KV数据库系统)阅读笔记
    • C++20 Coroutine
    • libcopp merge boost.context 1.69.0
    • Google去中心化分布式系统论文三件套(Percolator、Spanner、F1)读后感
    • Rust玩具-企业微信机器人通用服务
  • 2018
    • 使用ELK辅助监控开发测试环境服务质量和问题定位
    • Webpack+vue+boostrap+ejs构建Web版GM工具
    • 2018年的新通用伪随机数算法(xoshiro / xoroshiro)的C++(head only)实现
    • Rust的第二次接触-写个小服务器程序
    • 理解和适配AEAD加密套件
    • atsf4g-co的进化:协程框架v2、对象路由系统和一些其他细节优化
    • 协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比
    • 可执行文件压缩
    • 初识Rust
    • 使用restructedtext编写xresloader文档
    • atframework的etcd模块化重构
    • C++的backtrace
  • 2017
    • ECDH椭圆双曲线(比DH快10倍的密钥交换)算法简介和封装
    • protobuf-net的动态Message实现
    • pbc的proto3接入
    • atgateway内置协议流程优化-加密、算法协商和ECDH
    • 整理一波软件源镜像同步工具+DevOps工具
    • Blog切换到Hugo
    • libcopp v2的第一波优化完成
    • libcopp(v2) vs goroutine性能测试
    • libcopp的线程安全、栈池和merge boost.context 1.64.0
    • GCC 7和LLVM+Clang+libc++abi 4.0的构建脚本
    • libatbus的几个藏得很深的bug
    • 用cmake交叉编译到iOS和Android
    • 开源项目得一些小维护
    • atapp的c binding和c#适配
    • 对象路由系统设计
    • 2016年总结
    • 近期的一个协程流程BUG
  • 2016
    • 重写了llvm+clang+libc++和libc++abi的构建脚本
    • atsf4g完整游戏工程示例
    • atframework基本框架已经完成
    • 游戏服务器的不停服更新
    • 对atbus的小数据包的优化
    • Android和IOS的TLS问题
    • pbc的一个陈年老BUG
    • boost.context-1.61版本的设计模型变化
    • 接入letsencrypt+全面启用HTTP/2
    • 理解Raft算法
    • libatbus基本功能及单元测试终于写完啦
    • 博客文章和文档迁移到gitbook
  • 2015
    • 博客文章和文档迁移到gitbook
    • 给客户端写得LRU缓存
    • 近期活动比较零散
    • 关于BUS通信系统的一些思考(三)
    • 针对Java JIT的优化(转表工具:xresloader)
    • libcopp更新 (merge boost 1.59 context)
    • 小记最近踩得两个C++坑
    • Redis全异步(HA)Driver设计稿
    • Vim常用命令
    • 关于firewalld和systemd的一些命令速记
    • Jenkins(hudson)插件记录
    • 我们的Lua类绑定机制
    • LLVM+Clang+Libcxx+Libcxxabi(3.6)工具链编译(完成自举编译)
    • 回顾2014
    • Android NDK undefined reference to ___tls_get_addr 错误
    • gitlab腾讯企业邮箱配置
  • 2014
    • 回顾2013
    • C++11动态模板参数和type_traits
    • C++又一坑:动态链接库中的全局变量
    • tolua++内存释放坑
    • [转]类似github的框架
    • Lua性能分析
    • 集成Qt Webkit 到cocos2d-x
    • Gitlab环境搭建小计
    • 近期研究VPN的一些记录(OpenVPN,pptp,l2tp)
    • LLVM + Clang + Libcxx + Libcxxabi 工具链编译
    • 关于BUS通信系统的一些思考(二)
    • 关于BUS通信系统的一些思考(一)
    • [libiniloader] Project
    • 记录一些在线编辑器
    • [WP Code Highlight.js] Project
    • 再议 C++ 11 Lambda表达式
    • 基于Chrome插件的开发工具链
    • [ACM] HDU 1006 解题报告
    • Linux 编译安装 GCC 4.9
    • 又碰到了这个解谜游戏,顺带记下地址
    • 简单C++单元测试框架(支持一键切到GTest或Boost.Test)
    • 捣鼓一个协程库
  • 2013
    • std和boost的function与bind实现剖析
    • 不知道是哪一年的腾讯马拉松题目 照片评级 解题报告
    • Lua 挺好用的样子
    • VC和GCC成员函数指针实现的研究(三)
    • VC和GCC成员函数指针实现的研究(二)
    • VC和GCC内成员函数指针实现的研究(一)
    • 一个C++关于成员变量偏移地址的小Trick
    • ptmalloc,tcmalloc和jemalloc内存分配策略研究
    • POJ 2192 Zipper HDU 2059 龟兔赛跑
    • 从Javascript到Typescript到Node.js
    • 网络编程小结
    • 试试Boost.Asio
    • Lnmp yum 安装脚本 (for CentOS)
    • ARM 交叉编译环境搭建
    • Linux 编译安装 GCC 4.8
    • [记录]虚拟硬盘的压缩|磁盘写零
  • 2012
    • Boost.Spirit 初体验
    • “C++的90个坑”-阅读笔记
    • AC自动机
    • C++ 标准过渡期
    • 程序员修炼之道 -- 阅读笔记
    • [转载]狼与哈士奇
    • C++ 新特性学习(八) — 原子操作和多线程库[多工内存模型]
    • C++ 新特性学习(七) — 右值引用
    • 理解Protobuf的数据编码规则
    • 忆往昔ECUST的ACM时代
    • Linux编译安装GCC 4.7
    • JSON显示库 -- showJson (Javascript)
    • C++ 新特性学习(六) — 新的字符串编码和伪随机数
    • C++ 新特性学习(五) — 引用包装、元编程的类型属性和计算函数对象返回类型
    • C++ 新特性学习(四) — Bind和Function
  • 2011
    • C++ 新特性学习(三) — Regex库
    • C++ 新特性学习(二) -- Array、Tuple和Hash库
    • C++ 新特性学习(一) -- 概述+智能指针(smart_ptr)
    • Linux 和 Windows PowerShell 常用工具/命令 记录
    • 非常帅气的Linq to sql
    • 2011 Google Code Jam 小记
    • C++总是很神奇
    • 大学生创新项目[国家级]经费使用记录
    • 常用官方文档整理
    • 我们学校的IPV6很不错嘛
  • 2010
    • 线段树相关问题 (引用 PKU POJ题目) 整理
    • 2010 ACM 赛前笔记
    • POJ PKU 2596 Dice Stacking 解题报告
    • POJ PKU 3631 Cuckoo Hashing 解题报告
    • POJ PKU 1065 Wooden Sticks 3636 Nested Dolls 解题报告
    • HDU 3336 Count the string 解题报告
    • Hash模板 个人模板
    • ZOJ 3309 Search New Posts 解题报告
    • POJ PKU Let's Go to the Movies 解题报告
    • 注册表常用键值意义
    • PKU POJ 1724 ROADS 解题报告
    • 《神奇古今秘方集锦》&《民间秘术大全》
    • PKU POJ 1720 SQUARES 解题报告
    • POJ PKU 2155 Matrix 解题报告
    • PKU POJ 1141 Brackets Sequence 解题报告
    • PKU POJ 2728 Desert King 解题报告
    • PKU POJ 2976 Dropping tests 解题报告
    • PKU POJ 3757 Simple Distributed storage system 解题报告
    • GCD Determinant 解题报告
    • Southeastern European 2008 Sky Code 解题报告
    • HDU HDOJ 3400 Line belt 解题报告
    • 线性筛法求质数(素数)表 及其原理
    • HDU HDOJ 3398 String 解题报告
    • 树状数组模块(个人模板)
    • 浙江理工 省赛总结 team62 By OWenT of Coeus
    • POJ PKU 3659 Cell Phone Network 解题报告
    • USACO 2008 March Gold Cow Jogging 解题报告
    • C#格式化输出(记录)
    • 参加有道难题笔记
    • POJ PKU 2446 Chessboard 解题报告
    • POJ PKU 1986 Distance Queries 解题报告
    • 计算几何算法概览[转载]
    • 关于差分约束(转载)
    • POJ PKU 2826 An Easy Problem?! 解题报告
    • 数论模板(个人模板)
    • 简易四则运算(ACM个人模板)
    • Catalan 数
    • The 35th ACM/ICPC Asia Regional Tianjin Site —— Online Contest 1009 Convex 解题报告
    • JQuery扩展插件--提示信息
    • ACM 计算几何 个人模板
    • 解析网站字符串型参数 Javascript QueryString 操作 TQueryString类
    • POJ PKU 1474 Video Surveillance 解题报告
  • 2009
    • 模式匹配(kmp)个人模板
    • 并查集 模板
    • POJ 3267 The Cow Lexicon 解题报告
    • C/C++语言常用排序算法
    • POJ 2606 Rabbit hunt 2780 Linearity 1118 Lining Up 解题报告
    • 打造最快的Hash表(转) [以暴雪的游戏的Hash为例]
    • ECUST 09年 校赛个人赛第六,七场总结
    • ECUST 09年 校赛个人赛第三场部分解题报告(A,D,F,I)
    • 牛顿迭代解方程 ax^3+bX^2+cx+d=0
    • 09年8月9日 ECUST ACM 练习赛总结
    • 连接最多点直线 (OWenT 个人模板)
    • 点到直线距离 和 线段间最短距离 (OWenT 模板)
    • ECUST 09年 校赛个人训练赛第五场总结
    • ECUST 09年 校赛个人赛第八场(最后一场)总结
    • 09年8月14日 ECUST ACM 练习赛总结
    • 矩阵相关 (增强中)
    • Prime最小生成树(个人模板)
    • 最长单调子序列 复杂度nlog(n)
    • POJ PKU 2549 Sumsets 解题报告
    • POJ PKU 3277 City Horizon 解题报告
    • 我的ACM生涯
    • POJ PKU 2528 Mayor's posters 解题报告
    • POJ PKU 2378 Tree Cutting 解题报告
    • POJ PKU 1990 MooFest 解题报告
Powered by GitBook
On this page
  • libcopp 的由来
  • 相似方案
  • 设计目标
  • 基本原理和实现架构
  • 关于栈池分配器
  • v1 -> v2的架构变化
  • 性能对比
  • 工具集成
  • CMake Module
  • vcpkg
  • 单元测试和压力测试
  • 文档
  • 后续规划
  • 开源和交流

Was this helpful?

  1. 2019

跨平台协程库 - libcopp 简介

PreviousPALM Tree - 适合多核并发架构的B+树 - 论文阅读小记NextC++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比)

Last updated 5 years ago

Was this helpful?

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

Github: Document:

的由来

协程的概念并不是什么非常新颖的东西,最早有做 的想法的时候,是听了微信分享的 。但是我们游戏业务里大部分的实现都还是走的事务和Task/Step的模型,特别是C++上异步调用非常不直观。而这种协程的方法可以比较容易地把接口设计得很简洁,而且后续的功能集成上也很容易不破坏原有的API约定,还有微信这么大的业务背书,我们就想在游戏业务里也使用这种方案。我们先是预研了一些类似的方案,但是都不是特别满意。

相似方案

首先是 。 其实文档比较少,有些使用方式要看它的代码,而且是只支持Linux,但是对编译器的要求还不太明确,而我们项目组有时候会用一些比较新的东西,也是怕后面兼容性上不一定能保证。 一大特色是它的共享栈功能和对IO API的hook。前者的设计模式其实我是不太赞同的,因为它会留下一些坑(比如不能传出栈上变量到外部使用),而且对性能影响很大;后者其实对我们的用处不大,因为我们很少会直接去使用那些底层API,而是会接入很多库或者其他接入层来使用,更需要的是容易自定义。另外 开源出来的仓库似乎几乎没有测试,只有一些sample,总给人一种不太放心的感觉。

我们还研究了 ,现在 又有了新的 和 库,但是当时还没有。 库的兼容性和测试都一流,而且整个编程风格都是C++的,性能足够高,看起来非常良好。但是当时 只有很底层的接口,使用上并不是很方便,另外 对上下文对象管理的部分其实适配上也有一些问题。比如当时我们测过一些环境里,编译器版本比较高,但是缺失STL的TLS实现,就会直接链接不过。另外 最大的问题是它依赖整个 的平台和环境检测代码,非常重(即便用 提取依赖,仍然非常重)。我们其实非常排斥为了引入一个小组件,而导入一个超大规模的框架的。而后来出现的 我认为实现架构上我觉得是有一些问题的,兼容性更差,实用性也不好,现在已经 deprecated 掉了, 再后来的 我之前初步看了一下,感觉结构和 的 部分差不多。

在当时 当时也算是一个比较完善的协程框架了。它做了很多语法糖,上手很简单。当时它也做了和 一样的共享栈功能,但是后来作者不建议使用了,不知道是不是和我们一样的想法。 也是依赖的 , 但是当时它还没有打包一份出来自己维护,所以也算是依赖很重的框架。它的自定义接入层很简单,但是其实底层是绕了一圈,而且我们的业务需要自己控制调度层,而它提供的调度层我们似乎并不是很容易剥离,即便剥离了也会有一部分不必要的开销。

直接基于linux的swap_context和make_context的方案也是不是很完整,还是需要自己封装而且性能不太令人满意。其他的方案其实大同小异,我们就没有逐个去看了,最终还是选择了自己实现一个。于是 就诞生了。

设计目标

我设计 的时候优先还是考虑到我涉及的业务类型(游戏服务器)的。 首先我当时的想法是,既然用协程,就是为了简化业务开发人员的心智负担,并且能够容易排查问题,而且非共享栈带来的问题可以通过其他手段消除掉,所以没有去实现共享栈的功能(其实早期是预留了实现共享栈的空间的,后来觉得没必要去掉了)。

在游戏业务服务器里,会有需要接入各种各样的SDK和调度方式,所以容易集成其他系统就成了最大的优先级。

跨平台的特性是为了我们当时不同人都有自己的开发喜好,每个人都有自己喜欢的工具、环境和流程,我希望是能够适配大家的环境,提供可用且功能一致的版本,在这个基础上,我们线上业务是运行在Linux上的,所以Linux上性能最大化也是优先考虑的。

在设计 的时候,我也是尽量按照C++的设计风格。因为我觉得现代C++的很多工具对我们排除一些初级错误很有帮助(比如 static_assert ),所以在实现 的过程中,我们是会检测环境并且尽可能地使用 C++ 的一些新特性来优化性能或是规避问题;另外,因为整体架构和一些编程方法是可能随着时间而演进的,所以我们设计 还会尽可能地让其内部的组件,是可单独拆卸下来的,并且容易剥离和重组,这点一定程度上参考了 的设计;同时我们也会注重单元测试,保持比较高的覆盖率,这样对以后的修改能够更放心一些。

所以下面就是我们总结的设计目标了:

  • 跨平台(Linux/macOS/Windows/MinGW + GCC/Clang/MSVC)

  • 高性能

  • 易集成,不容易误用,线程安全

  • 功能简洁,正交性

  • 容易定制化

  • 依赖少

  • Modern C++

上面的目标其实也不只是游戏服务器业务的需求点,而是想要保持足够灵活。这样如果其他业务要使用,只需要做少量的接入工作即可。

下面是一些常用方案的简单对比(可能理解上会有差错请见谅):

协程库/方案

跨平台

原生线程安全支持

外部依赖

创建性能

切换性能

扩展性

原生IO支持

设计模式

Windows/Linux/macOS等

所有接口支持

无

极好

很好

很好,易扩展,模块化设计

无

C++,仿promise

Windows/Linux/macOS等

否

很重,依赖boost

不一定

很好

很好,原生功能简单

无

C++,需要二次封装

Windows/Linux/macOS等

否

很重,依赖boost

很好

很好

很好,较复杂,模拟IO回调

std/boost

C++,pull/push回调模型

Windows/Linux/macOS等

部分,由内置调度层管理

很重,依赖boost

一般

很好

IO API Hook

仿goroutine

仅支持x86/x86_64的Linux

否

无

很好

不一定

一般,内置静态栈池

IO API Hook

无,需要二次封装

linux ucontext

仅支持Linux

否

无

一般

一般

一般,只有上下文切换

无

无,需要二次封装

仅支持x86/x86_64的Linux

否

无

极好

极好

一般,只有上下文切换

无

无,需要二次封装

目前仅支持Windows+MSVC

部分,由内置调度层管理

依赖C++20

很好

极好

ASIO

仿goroutine,仿promise

Windows/Linux/macOS等

否

依赖C++20

极好

极好

很好,但非常复杂

部分STL

无栈协程,需要二次封装

Windows/Linux/macOS等

否

golang

好

一般

很好,易扩展

基本原理和实现架构

关于栈池分配器

  • 栈对象采用 FILO 的模式

  • 高负载会自动提高栈池容量,并且带有数量和地址空间大小上限

  • 低负载会自动减小容量,并且带有数量和地址空间大小下限

  • 可以控制每个tick的回收数量,防止长时间 Stop The World

v1 -> v2的架构变化

性能对比

{ "type": "bar", "data": { labels: ['协程数:1,栈大小16KB', '协程数:1000,栈大小2MB', '协程数:30000,栈大小64KB'], "datasets": [ { "label": "C++20 Coroutine - Clang 切换耗时", "borderColor": "rgba(139, 0, 0, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [5, 6, 9], "type": 'line' }, { "label": "C++20 Coroutine - MSVC 切换耗时", "borderColor": "rgba(0, 0, 139, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [10, 14, 28], "type": 'line' }, { "label": "libcopp 切换耗时", "borderColor": "rgba(0, 139, 139, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [34, 80, 223], "type": 'line' }, { "label": "libcopp+动态栈池 切换耗时", "borderColor": "rgba(184, 134, 11, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [32, 77, 213], "type": 'line' }, { "label": "libcopp+libcotask 切换耗时", "borderColor": "rgba(169, 169, 169, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [50, 141, 389], "type": 'line' }, { "label": "libcopp+libcotask+动态栈池 切换耗时", "borderColor": "rgba(189, 183, 107, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [49, 134, 371], "type": 'line' }, { "label": "libco+静态栈池 切换耗时", "borderColor": "rgba(139, 0, 139, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [84, 168, 450], "type": 'line' }, { "label": "libco(共享栈4K占用) 切换耗时", "borderColor": "rgba(85, 107, 47, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [83, 529, 1073], "type": 'line' }, { "label": "libco(共享栈8K占用) 切换耗时", "borderColor": "rgba(255, 140, 0, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [82, 828, 1596], "type": 'line' }, { "label": "libgo 2019年9月master分支 切换耗时", "borderColor": "rgba(153, 50, 204, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [53, 120, 237], "type": 'line' }, { "label": "libgo 2018年版本 with boost 切换耗时", "borderColor": "rgba(233, 150, 122, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [197, 124, 441], "type": 'line' }, { "label": "libgo 2018年版本 with ucontext 切换耗时", "borderColor": "rgba(143, 188, 143, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [529, 482, 921], "type": 'line' }, { "label": "goroutine(golang) 切换耗时", "borderColor": "rgba(255, 20, 147, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [425, 710, 1047], "type": 'line' }, { "label": "linux ucontext 切换耗时", "borderColor": "rgba(72, 61, 139, 1)", "fill": false, "yAxisID": 'y-axis-2', "data": [435, 509, 890], "type": 'line' }, { "label": "C++20 Coroutine - Clang 创建耗时", "backgroundColor": "rgba(255, 0, 0, 1)", "yAxisID": 'y-axis-1', "data": [null, 130, 136] }, { "label": "C++20 Coroutine - MSVC 创建耗时", "backgroundColor": "rgba(0, 0, 255, 1)", "yAxisID": 'y-axis-1', "data": [null, 407, 369] }, { "label": "libcopp 创建耗时", "backgroundColor": "rgba(0, 255, 255, 1)", "yAxisID": 'y-axis-1', "data": [null, 4100, 3800] }, { "label": "libcopp+动态栈池 创建耗时", "backgroundColor": "rgba(218, 165, 32, 1)", "yAxisID": 'y-axis-1', "data": [null, 96, 212] }, { "label": "libcopp+libcotask 创建耗时", "backgroundColor": "rgba(128, 128, 128, 1)", "yAxisID": 'y-axis-1', "data": [null, 4100, 4200] }, { "label": "libcopp+libcotask+动态栈池 创建耗时", "backgroundColor": "rgba(240, 230, 140, 1)", "yAxisID": 'y-axis-1', "data": [null, 134, 256] }, { "label": "libco+静态栈池 创建耗时", "backgroundColor": "rgba(255, 0, 255, 1)", "yAxisID": 'y-axis-1', "data": [null, 3900, 4200] }, { "label": "libco(共享栈4K占用) 创建耗时", "backgroundColor": "rgba(128, 128, 0, 1)", "yAxisID": 'y-axis-1', "data": [null, 3900, 3900] }, { "label": "libco(共享栈8K占用) 创建耗时", "backgroundColor": "rgba(255, 165, 0, 1)", "yAxisID": 'y-axis-1', "data": [null, 4000, 3900] }, { "label": "libgo 2019年9月master分支 创建耗时", "backgroundColor": "rgba(218, 112, 214, 1)", "yAxisID": 'y-axis-1', "data": [null, 8300, 5500] }, { "label": "libgo 2018年版本 with boost 创建耗时", "backgroundColor": "rgba(250, 128, 114, 1)", "yAxisID": 'y-axis-1', "data": [null, 5300, 2300] }, { "label": "libgo 2018年版本 with ucontext 创建耗时", "backgroundColor": "rgba(46, 139, 87, 1)", "yAxisID": 'y-axis-1', "data": [null, 7000, 2700] }, { "label": "goroutine(golang) 创建耗时", "backgroundColor": "rgba(106, 90, 205, 1)", "yAxisID": 'y-axis-1', "data": [null, 1000, 1000] }, { "label": "linux ucontext 创建耗时", "backgroundColor": "rgba(112, 128, 144, 1)", "yAxisID": 'y-axis-1', "data": [null, 4400, 4800] }] }, "options": { title: { display: true, text: '切换/创建耗时(越小越好)' }, scales: { yAxes: [{ type: 'linear', display: true, scaleLabel: { display: true, labelString: "切换耗时(单位:纳秒)" }, position: 'left', id: 'y-axis-2', gridLines: { drawOnChartArea: false }, ticks: { callback: function(value, index, values) { return value + ' ns'; } } }, { type: 'logarithmic', display: true, scaleLabel: { display: true, labelString: "创建耗时(单位:纳秒)" }, ticks: { autoSkip: true, callback: function(value, index, values) { for (var idx in values) { var tv = values[idx]; if (tv < value && Math.floor(Math.log(value)) == Math.floor(Math.log(tv))) { return null; } } return value + ' ns'; } }, position: 'right', id: 'y-axis-1', }], } } }

工具集成

CMake Module

其他的发布环境下使用还是需要使用原始的 install 完后指定include和lib目录的形式。

vcpkg

单元测试和压力测试

文档

后续规划

开源和交流

较好,内置调度器,模拟

较好,内置调度器,模拟

属于对称式有栈协程,有栈协程的基本原理很简单。不同平台,不同架构的架构下的 对函数调用都有一定的规范,比如 x86_64 的基本规范是要求函数的被调方保留好上一帧的 R12 、 R13 、 R14 、 R15 、 RBX 、 RBP (其他特殊的功能比如TSX或者Linux下GCC的动态栈还需要有其他的内容), 在函数返回后还原,然后函数栈的起始地址有对齐要求。 而有栈协程即是把这些要求被调方处理的寄存器内容保存在栈上,然后直接jmp到新的执行地址即可。在跳回来以后需要还原这些寄存器,对调用方来说,就像调用了一个函数一样。

我没有挨个平台去看 的文档,所以为了实现跨平台,我在 里是直接引用了 里的 fcontext 部分。但是我把它的平台判定和汇编层代码剥离出来并且重命名了符号,这样可以不依赖庞大的 库,而且如果哪个项目要用 也不会冲突。项目里的 也是这个原因而存在。

大体上分为两部分.第一部分是 ,这部分主要负责偏底层的协程上下文管理、栈管理; 第二部分是 , 这部分主要用于一些更高层面的设计模式和基于协程的任务模型(包括但不限于进程内 唯一ID分配 、 超时管理 、 await语义 、 自定义参数 的关联和分配等等),还包含一个 task_manager 用于基于ID的统一管理和提供超时管理。

其中 里还分为 栈分配器 、 执行上下文管理 和 用户自定义数据 , 其中 栈分配器 是可自定义的,只需要类似 std::allocator 实现几个接口即可,我们也提供了几个内置的分配器供直接使用,包括 通过malloc分配 、 mmap/unmap(Windows下是VirtualAlloc/VirtualFree) 、 自定义指定内存地址的分配器 、 Linux下的动态增长栈分配器 和 动态栈池分配器 。这里面 动态栈池分配器 还支持搭配底层使用上面其他的分配器。 是可选的,如果业务有自己的任务系统也可以不用,并且可以通过编译选项完全关闭, 而在 里,ID分配器 也是可以自定义的。 这样所使用的业务可以根据自己的需要来选择、搭配或是自定义用自己项目里统一的管理系统。

栈池分配器起源于我大规模使用 的一个项目压力测试的时候,发现实际的CPU占用和预期相差比较大(当时预期 的开销时1%左右,但是实际大约 10%)。 后来分析出来大部分的开销耗在了 缺页中断 上面。 和 也有这个问题,而 分配的栈一般都是malloc出来的而且会复用不太会出现这种情况。 其实在 里如果选择使用 通过malloc分配 或者 自定义指定内存地址的分配器 也不会有这个问题。但是我们希望项目中发现问题(特别是栈溢出)为第一优先级,所以通常我们都会选用 mmap/unmap(Windows下是VirtualAlloc/VirtualFree) 。这时候这个问题就凸显出来了,因为每次分配的时候都重新建立地址映射,那么物理页肯定就被释放了。 为了解决这个问题,我们就写了一个 动态栈池分配器 。这个分配器基本不需要设置,接入也只需要简单地改两处调用的代码即可。而之所以是 动态 的是因为我们项目中一台机器上可能会搭建很多测试环境,这些环境往往都是低负载、版本不同且提供给很多不同的人用的,所以可以减少低负载服务的内存占用,并且尽可能多地利用好已有 内存映射的逻辑地址 。 所以最终 动态栈池分配器 提供的功能是:

有一次比较大规模的重构,其实是伴随着 1.61 版本的一次底层结构大重构。(关于这次 的变化的细节可以参见)。这次 的大重构把它的底层 由对称式协程上下文改成了非对称式 ,并且接口上对参数透传更加友好。这其实增大了底层上下文使用者的很大的灵活性。但是 在应用中还是使用的对称式 ,而且对称式理解和管理起来更方便,所以 还是还原了对称式的做法。另外新模型的 的还增加了一个 ontop 事件,用于便于在切入前做一些事情。但是 有自己的事件管理,并且还没发现实用的场景,所以还没有使用这个功能。

其他的变更可以参见

随着这次的结构大重构,我就给 做了一些其他优化。大致的内容是使用分配的栈空间来存放 自身所需的数据结构,并且留了接口给使用者指定自定义的私有数据块。+的数据结构对齐以后占用空间大约 是192字节 ,但是由于栈空间都是对齐到4K的,所以 内部对象都会落在CPU Cache的同一组 Cache Line上,导致压力测试的场景下命中率有所下降。另外还使用自己实现的侵入式智能指针代替原有使用的 std::shared_ptr ,这个的目的是简化接口并且在不需要多线程的时候提供给用户选项关闭多线程安全,这样可以减小智能指针和内部为了保证线程安全的原子操作导致的CPU Cache强制失效。

这一次的重构的更多细节可以参见 。

后来还有一些优化是针对CPU代码缓存命中率的优化和对对齐的优化,这些对实际应用中影响比较小(主要是实际业务中使用不会是压力测试的时候那么“纯净”的环境)所以没有单独写文章记录。在开发 的过程中的一些比较重要的节点和当时的思考都在 了。

有栈协程比起无栈协程的一个劣势是有代码段和数据段的跳转,不利于编译器的分析和优化和系统缓存的命中,所以性能上肯定是比不上无栈协程(比如 )。但是有栈协程比无栈协程也有一个非常大的优势在于对API设计完全没有要求,框架开发者可以做到对上层业务完全透明。在有栈协程中 不说性能最好,也算是第一梯队的了。之前我做过一个协程的性能对比,也包含了 的无栈协程。

更细节的详情可以参见: 和

在设计之初也是希望使用的时候能够简单方便,所以对一些流行的工具做了支持。

本身是使用 管理的,所以理所当然会提供对 的 find_package 的支持。最早 是提供了一个 FindLibcopp.cmake 文件以供使用。但是后来 3.0 开始推荐使用新的 Module 模型,通过提供 XXXConfig.cmake/XXX-config.cmake 和 XXXConfigVersion.cmake/XXX-config-version.cmake 所以现在的 install 流程也会安装用于支持 find_package(Libcopp) 的配置文件。

是 Microsoft 开发的一个C++的跨平台包管理工具,整个系统也是基于 的,很容易就可以支持了。现在已经可以通过 的 vcpkg install libcopp 来安装 。

里使用的是一个自己实现的轻量级单元测试框架,这样可以提供基本的测试功能并减少依赖。这个简易的单元测试框架也提供了编译开关来切换到 或者 。然后在CI工具里集成了单元测试和压力测试以便观测一些修改对性能和API接口的影响。

目前 的文档不算特别好。现在是通过 和 自动生成的API文档并发布在 。README里和sample里目前仅有一些典型的使用方法,反倒是单元测试代码里有所有的接口。现在对一些稍微复杂的使用可能还是需要用户看单元测试来做参照。

已经正式进入了草案,并且它在运行性能、内存占用、语法糖上都有绝对的优势。虽然说离能够正式使用还很遥远,但是底层的库的适配都需要先行。 的下一步计划是思考如何能够和 搭配起来,甚至后面如果往 上迁移能够平滑地进行。其实要实现让 支持 的 co_await 并不是很困难,稍微麻烦的地方在于如何判定编译器和环境是否支持。而另一个最大的困难在于像如何使用或者过渡到 的上下文模型。的一大问题是接入非常麻烦且几个模块的关系非常不直观,如果哪天正式项目中可以使用了肯定是包了好几层的。那么对于 来说就是以后对 如何封装和如果能够较容易地过渡过去。

另外就是一些其他环境兼容性检测,和周边工具的支持比如 和迁移CI到 。

刚开始的时候就是开源的,当时并没有什么特别的想法,只是说这样存取代码和交流会更方便些。 开源出来以后有一些小伙伴们提出过一些很实用的意见和建议,我也根据实际使用场景针对性地做了各种优化,也算查漏补缺吧。也同时特别感谢下 和 都对一些实现细节提供了很多很有用的建议,也欢迎其他有兴趣的小伙伴们一起提意见和交流哈。

libcopp
libcopp
libcopp
https://github.com/owt5008137/libcopp
https://libcopp.atframe.work/
libcopp
libcopp
libco
libco
libco
libco
libco
boost.context
boost
boost.coroutine
boost.coroutin2
boost
boost.context
boost.context
boost.context
boost.context
boost
bcp
boost.coroutine
boost.coroutin2
libcopp
copp
libgo
libco
libgo
boost.context
libcopp
libcopp
libcopp
libcopp
libcopp
boost.context
libcopp
ABI
ABI
libcopp
boost.context
boost
boost
BOOST_LICENSE_1_0.txt
libcopp
copp
cotask
copp
cotask
cotask
libcopp
libcopp
libgo
boost.coroutine2
libco
libcopp
libcopp
boost.context
boost.context
《boost.context-1.61版本的设计模型变化》
boost.context
libcopp
libcopp
boost.context
libcopp
boost.context
《libcopp的线程安全、栈池和merge boost.context 1.64.0》
libcopp
libcopp
copp
cotask
libcopp
《libcopp v2的第一波优化完成》
libcopp
https://cn.bing.com/search?q1=site%3aowent.net&q=libcopp
C++20 Coroutine
libcopp
C++20 Coroutine
《C++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比)》
《协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比》
libcopp
libcopp
cmake
cmake
libcopp
cmake
vcpkg
cmake
vcpkg
libcopp
libcopp
boost.test
gtest
libcopp
doxygen
pacman
https://libcopp.atframe.work/
C++20 Coroutine
libcopp
C++20 Coroutine
C++20 Coroutine
libcopp
C++20 Coroutine
C++20 Coroutine
C++20 Coroutine
libcopp
C++20 Coroutine
cpack
Github Action
libcopp
mutouyun
yuanzhubi
libcopp/libcotask
boost.context
boost.coroutine2
libgo
channel
libco
call_in_stack
librf
channel
C++20 Coroutine
goroutine
golang
channel
../2018/1806-02.dot.png