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
  • 前言
  • 压力测试机环境
  • 测试代码
  • 测试结果及对比
  • 最后

Was this helpful?

  1. 2019

C++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比)

Previous跨平台协程库 - libcopp 简介Next尝鲜Github Action

Last updated 4 years ago

Was this helpful?

前言

之前写了 和 ,但是一直没写 的测试报告。

现在的草案版本比我当时写 的时候有了一点点更新, 上有文档了() 。里面列举的标准文档是,这个文档目前还没完工,详情可以看他的来源。不过内容上暂时还没有太大的变化,今天我就照着之前的方式来benchmark一波 吧。

压力测试机环境

为了方便比较,我更新了一下之前在 里的测试项目的版本。Windows环境仅仅是为了测试MSVC下的性能,因为GCC还不支持所以Linux下是使用Clang编译的。

环境名称

值

系统

Linux kernel 3.10.107(Docker)

CPU

Intel(R) Xeon(R) Gold 61xx CPU @ 2.50GHz * 48

L1 Cache

64Bytes*64sets*8ways=32KB

系统负载

0.19 0.25 0.27

内存占用

3.5GB(used)/125GB(sum)

CMake

3.15.2

GCC版本

9.2.0

Clang版本

9.0.0

libco版本

03ba1a453c266b76e1c01aa519621ef7db861500 (20190902)

libcopp

1.2.1 (20191004)

cbdf26bbf568a72e81fdd7ec390ddbcae5d5dd92 (20190822)

环境名称

值

系统

Windows 10 Pro 1903 (2019 Sept)

CPU

Intel(R) Core(TM) i7-8700 @ 3.20GHz * 12

L1 Cache

64Bytes*64sets*8ways=32KB

系统负载

低于 10%

内存占用

8.2GB(used)/16.7GB(cached)/38.7GB(free)

MSVC版本

MSVC v142 - VS 2019 C++ x86/x64 (14.23)

测试代码

Clang编译命令: $LLVM_CLANG_PREFIX/bin/clang++ -std=c++2a -O2 -g -ggdb -stdlib=libc++ -fcoroutines-ts -lc++ -lc++abi -Wl,-rpath=$LLVM_CLANG_PREFIX/lib/ test.cpp

MSVC编译命令: cl /nologo /O2 /std:c++latest /Zi /MDd /Zc:__cplusplus /EHsc /await test.cpp

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <inttypes.h>
#include <stdint.h>
#include <vector>
#include <memory>
#include <iostream>

#include <experimental/coroutine>

#include <chrono>
#define CALC_CLOCK_T std::chrono::system_clock::time_point
#define CALC_CLOCK_NOW() std::chrono::system_clock::now()
#define CALC_MS_CLOCK(x) static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(x).count())
#define CALC_NS_AVG_CLOCK(x, y) static_cast<long long>(std::chrono::duration_cast<std::chrono::nanoseconds>(x).count() / (y ? y : 1))


static std::vector<std::pair<int*, std::experimental::coroutine_handle<> > > g_test_rpc_manager;

struct test_custom_coroutine_data;
struct test_custom_coroutine {
    using data_ptr = std::unique_ptr<test_custom_coroutine_data>;

    struct promise_type {
        data_ptr refer_data;
        char fake_cache_miss_[64 - sizeof(test_custom_coroutine_data*)];
        promise_type();

        static test_custom_coroutine get_return_object_on_allocation_failure();

        test_custom_coroutine get_return_object();

        std::experimental::suspend_always initial_suspend();

        std::experimental::suspend_always final_suspend();

        void unhandled_exception();

        // 用以支持 co_return
        void return_void();

        // 用以支持 co_yield
        std::experimental::suspend_always yield_value(test_custom_coroutine_data*&);
    };

    // 下面的接入用侵入式的方式支持 co_await test_rpc_generator
    // MSVC 目前支持使用非侵入式的方式实现,但是clang不支持
    bool await_ready() noexcept;

    void await_resume();

    void await_suspend(std::experimental::coroutine_handle<promise_type>);

    int resume();
    void set_sum_times(int);
    bool is_done() const;
    test_custom_coroutine_data* data();
private:
    test_custom_coroutine(test_custom_coroutine_data*);
    test_custom_coroutine_data* data_;
    char fake_cache_miss_[64 - sizeof(test_custom_coroutine_data*)];
};

struct test_custom_coroutine_data {
    int sum_times;
    int yield_times;

    std::experimental::coroutine_handle<test_custom_coroutine::promise_type> handle;
};

test_custom_coroutine::promise_type::promise_type() {
    refer_data = std::make_unique<test_custom_coroutine_data>();
    refer_data->sum_times = 0;
    refer_data->yield_times = 0;
}
test_custom_coroutine test_custom_coroutine::promise_type::get_return_object_on_allocation_failure() {
    return test_custom_coroutine{ nullptr };
}

test_custom_coroutine test_custom_coroutine::promise_type::get_return_object() {
    return test_custom_coroutine{ refer_data.get() };
}

std::experimental::suspend_always test_custom_coroutine::promise_type::initial_suspend() {
    refer_data->handle = std::experimental::coroutine_handle<promise_type>::from_promise(*this);
    return std::experimental::suspend_always{}; // STL提供了一些自带的awaiter实现,我们其实很多情况下也不需要另外写,直接用STL就好了
}

std::experimental::suspend_always test_custom_coroutine::promise_type::final_suspend() {
    return std::experimental::suspend_always{}; // 和上面一样,也是STL自带的awaiter实现
}

void test_custom_coroutine::promise_type::unhandled_exception() {
    std::terminate();
}

// 用以支持 co_return
void test_custom_coroutine::promise_type::return_void() {
    refer_data->handle = nullptr;
}

// 用以支持 co_yield
std::experimental::suspend_always test_custom_coroutine::promise_type::yield_value(test_custom_coroutine_data*& coro_data) {
    // 每次调用都会执行,创建handle用以后面恢复数据
    if (nullptr != refer_data) {
        refer_data->handle = std::experimental::coroutine_handle<promise_type>::from_promise(*this);
        ++refer_data->yield_times;
    }

    coro_data = refer_data.get();
    return std::experimental::suspend_always{};
}

// 下面的接入用侵入式的方式支持 co_await test_custom_coroutine , 实际上benchmark过程中并没有用到
// MSVC 目前支持使用非侵入式的方式实现,但是clang不支持
bool test_custom_coroutine::await_ready() noexcept {
    // 准备好地标志是协程handle执行完毕了
    return !data_ || !data_->handle || data_->handle.done();
}

void test_custom_coroutine::await_resume() {
    // do nothing when benchmark
}

void test_custom_coroutine::await_suspend(std::experimental::coroutine_handle<promise_type>) {
    // do nothing when benchmark
    // 被外部模块 co_await , 这里完整的协程任务链流程应该是要把coroutine_handle记录到test_custom_coroutine
    // 在return_void后需要对这些coroutine_handle做resume操作,但是这里为了减少benchmark的额外开销和保持干净
    // 所以留空
}

int test_custom_coroutine::resume() {
    if (!await_ready()) {
        data_->handle.resume();
        return 1;
    }

    return 0;
}

void test_custom_coroutine::set_sum_times(int times) {
    if (data_) {
        data_->sum_times = times;
    }
}

bool test_custom_coroutine::is_done() const {
    return !(data_ && data_->handle);
}

test_custom_coroutine_data* test_custom_coroutine::data() {
    return data_;
}

test_custom_coroutine::test_custom_coroutine(test_custom_coroutine_data* d) : data_(d) {}

// 异步协程函数
test_custom_coroutine coroutine_start_main(test_custom_coroutine_data*& coro_data) {
    // create done

    // begin to yield
    while (coro_data != nullptr && coro_data->yield_times < coro_data->sum_times) {
        co_yield coro_data;
    }

    // finish all yield
    co_return;
}

// 这里模拟生成数据
bool coroutine_resume(std::vector<test_custom_coroutine>& in, long long& real_switch_times) {
    bool ret = false;
    for (auto& co : in) {
        real_switch_times += co.resume();
        if (!co.is_done()) {
            ret = true;
        }
    }

    return ret;
}

int main(int argc, char* argv[]) {
#ifdef __cpp_coroutines
    std::cout << "__cpp_coroutines: " << __cpp_coroutines << std::endl;
#endif
    puts("###################### C++20 coroutine ###################");
    printf("########## Cmd:");
    for (int i = 0; i < argc; ++i) {
        printf(" %s", argv[i]);
    }
    puts("");

    int switch_count = 100;
    int max_coroutine_number = 100000; // 协程数量

    if (argc > 1) {
        max_coroutine_number = atoi(argv[1]);
    }

    if (argc > 2) {
        switch_count = atoi(argv[2]);
    }

    std::vector<test_custom_coroutine> co_arr;
    std::vector<test_custom_coroutine_data*> co_data_arr;
    co_arr.reserve(static_cast<size_t>(max_coroutine_number));
    co_data_arr.resize(static_cast<size_t>(max_coroutine_number), nullptr);

    time_t       begin_time = time(NULL);
    CALC_CLOCK_T begin_clock = CALC_CLOCK_NOW();

    // create coroutines
    for (int i = 0; i < max_coroutine_number; ++i) {
        co_arr.emplace_back(coroutine_start_main(co_data_arr[i]));
        co_arr.back().set_sum_times(switch_count);
        co_data_arr[i] = co_arr.back().data();
    }

    time_t       end_time = time(NULL);
    CALC_CLOCK_T end_clock = CALC_CLOCK_NOW();
    printf("create %d coroutine, cost time: %d s, clock time: %d ms, avg: %lld ns\n", max_coroutine_number,
        static_cast<int>(end_time - begin_time), CALC_MS_CLOCK(end_clock - begin_clock),
        CALC_NS_AVG_CLOCK(end_clock - begin_clock, max_coroutine_number));

    begin_time = end_time;
    begin_clock = end_clock;

    // yield & resume from runner
    long long real_switch_times = static_cast<long long>(0);

    bool is_continue = true;
    while (is_continue) {
        is_continue = coroutine_resume(co_arr, real_switch_times);
    }
    // sub create - resume
    real_switch_times -= max_coroutine_number;

    end_time = time(NULL);
    end_clock = CALC_CLOCK_NOW();
    printf("switch %d coroutine contest %lld times, cost time: %d s, clock time: %d ms, avg: %lld ns\n", max_coroutine_number,
        real_switch_times, static_cast<int>(end_time - begin_time), CALC_MS_CLOCK(end_clock - begin_clock),
        CALC_NS_AVG_CLOCK(end_clock - begin_clock, real_switch_times));

    begin_time = end_time;
    begin_clock = end_clock;

    co_arr.clear();

    end_time = time(NULL);
    end_clock = CALC_CLOCK_NOW();
    printf("remove %d coroutine, cost time: %d s, clock time: %d ms, avg: %lld ns\n", max_coroutine_number,
        static_cast<int>(end_time - begin_time), CALC_MS_CLOCK(end_clock - begin_clock),
        CALC_NS_AVG_CLOCK(end_clock - begin_clock, max_coroutine_number));

    return 0;
}

测试结果及对比

组件(Avg)

协程数:1 切换开销

协程数:1000 创建开销

协程数:1000 切换开销

协程数:30000 创建开销

协程数:30000 切换开销

C++20 Coroutine - Clang

5 ns

130 ns

6 ns

136 ns

9 ns

C++20 Coroutine - MSVC

10 ns

407 ns

14 ns

369 ns

28 ns

组件(Avg)

协程数:1 切换开销

协程数:1000 创建开销

协程数:1000 切换开销

协程数:30000 创建开销

协程数:30000 切换开销

栈大小(如果可指定)

16 KB

2 MB

2 MB

64 KB

64 KB

C++20 Coroutine - Clang

5 ns

130 ns

6 ns

136 ns

9 ns

C++20 Coroutine - MSVC

10 ns

407 ns

14 ns

369 ns

28 ns

34 ns

4.1 us

80 ns

3.8 us

223 ns

32 ns

96 ns

77 ns

212 ns

213 ns

50 ns

4.1 us

141 ns

4.2 us

389 ns

49 ns

134 ns

134 ns

256 ns

371 ns

84 ns

3.9 us

168 ns

4.2 us

450 ns

83 ns

3.9 us

529 ns

3.9 us

1073 ns

86 ns

4.0 us

828 ns

3.9 us

1596 ns

-

4.0 us

9152 ns

3.9 us

11.5 us

53 ns

8.3 us

120 us

5.5 us

237 ns

197 ns

5.3 us

124 ns

2.3 us

441 ns

539 ns

7.0 us

482 ns

2.7 us

921 ns

425 ns

1.0 us

710 ns

1.0 us

1047 ns

linux ucontext

439 ns

4.4 us

505 ns

4.8 us

890 ns

来个直观一点的图:

{ "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', }], } } }

最后

欢迎有兴趣的小伙伴们一起交流哈。

版本

(20190903)

(依赖)

(20190819)

上手比较麻烦,所以测试代码那是真滴长。 co_await 的原理和 co_yield 是一样的,只是 co_await 多了一点点对封装类似 的支持,单纯的上下文切换仅使用 co_yield 就可以了。这样也更能公平地拿来和其他几个协程库对比。

其他库的测试代码在

C++20 裸测试的性能真是非常夸张地高,基本上性能已经赶上 这种对分支预测做优化的版本,并且还有不错的灵活性。这里性能测试的结果很好看一方面是 coroutine_handle<T> 的成员是个指针,再里面的管理上下文的部分我没法控制它的实现,所以没法模拟cache miss。另一方面也是由于它是使用operator new并且分析调用的函数需要多少栈来分配栈空间的,这样不会有内存缺页的问题(因为和其他的逻辑共享内存块),而且地址空间使用量也很小并且是按需分配的,也减少了系统调用的次数。还有一点影响比较大的是这次测试的C++20 Coroutine的代码全部是非线程安全的。而 在实际应用中是搭配上了线程安全检查和一些防止误用的状态检查的,全是atomic操作,甚至 那种加锁的线程安全的检查,性能会会受到一定影响。如果在实际应用C++20 Coroutine的时候也加上这些检查,估计性能会下降几倍,但是应该还是会比现在的成熟方案要快一些。

不过现阶段 使用上还有些限制,所有使用 co_await 或者 co_yield 的函数返回值必须有 promise_type 。 也就是说如果嵌套使用或者递归调用的话不能直接用上层的协程对象,一旦出现嵌套使用只能 co_await 然后新创建一个协程对象。比如调用链 func1()->func2()->func3()->func4() , 如果 func1 和 func4 是需要使用协程调用,要么得 func2 和 func3 也实现成协程,然后 func1 里 co_await func2() , 要么 func2 和 func3 把 func4 产生的 awaitable 对象透传回来,然后由 func1 来 co_await func2().awaitable 。 也就是说 func2 和 func3 对 func4 不能完全透明。这是 比不上 的地方。 这也是我前段时间思考给 接入 做Context管理的最大困难。

我们拿之前 对比过的其他库放一起来看:

+静态栈池

(共享栈4K占用)

(共享栈8K占用)

(共享栈32K占用)

2019年9月master分支

2018年版本 with boost

2018年版本 with ucontext

结论就不多说了,和 差不多,需要稍微提一下的是上面的 创建耗时 的时间不是线性而是对数的,因为几个库差距有点大,等差的图示太难区分了;另外测试条目里并不全在一个层面,有些是比较底层的接口,有的是已经接近工程化了。还有上面的测试结果受代码缓存命中率和数据缓存命中率影响比较大,除了 C++20 Coroutine 的测试以外,其他的都使用了一定的手段来让cache miss(更接近实际应用)。所以实际使用 C++20 Coroutine 的话切换性能应该是会比这个结果看起来差一些。不过参考 boost.context 的裸调用fcontext的上下文切换,cache不miss的时候大约是30ns左右,相比起来 C++20 Coroutine 还是很有优势的,而且 C++20 Coroutine 更大的优势在于创建性能和内存占用。

《协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比》
《C++20 Coroutine》
C++20 Coroutine
《C++20 Coroutine》
cppreference
https://en.cppreference.com/w/cpp/language/coroutines
P0912R5
N4775
C++20 Coroutine
《协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比》
C++20 Coroutine
libcotask
https://gist.github.com/owt5008137/1842b56ac1edd5a7db54590d41af1c44
call_in_stack
libcopp
libgo
《C++20 Coroutine》
《C++20 Coroutine》
libcopp
libcopp
《C++20 Coroutine》
《协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比》
《协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比》
Golang
1.13.1
Boost版本
libgo
1.71.1
libgo
libcopp
libcopp+动态栈池
libcopp+libcotask
libcopp+libcotask+动态栈池
libco
libco
libco
libco
libgo
libgo
libgo
goroutine(golang)