惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Forbes - Security
Forbes - Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
F
Fortinet All Blogs
B
Blog
T
The Blog of Author Tim Ferriss
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
Y
Y Combinator Blog
Microsoft Azure Blog
Microsoft Azure Blog
L
LangChain Blog
Recent Announcements
Recent Announcements
U
Unit 42
Martin Fowler
Martin Fowler
M
MIT News - Artificial intelligence
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The Register - Security
The Register - Security
Recorded Future
Recorded Future
C
Check Point Blog
V
V2EX
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
酷 壳 – CoolShell
酷 壳 – CoolShell
F
Full Disclosure
小众软件
小众软件
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
有赞技术团队
有赞技术团队
MongoDB | Blog
MongoDB | Blog
爱范儿
爱范儿
P
Proofpoint News Feed
罗磊的独立博客
量子位
D
Docker
博客园_首页
D
DataBreaches.Net
Project Zero
Project Zero
博客园 - 司徒正美
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - Franky
Security Latest
Security Latest
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
Netflix TechBlog - Medium
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 三生石上(FineUI控件)
H
Hackread – Cybersecurity News, Data Breaches, AI and More
大猫的无限游戏
大猫的无限游戏

I'm OWenT

国产大模型(GLM 5.1、Kimi K2.6)真实场景效果和 Coding Plan 额度测试 新版本libatapp的连接管理——从etcd服务发现到拓扑驱动的自动重连 新版本libatbus的设计变更——从树形路由到拓扑驱动 Protobuf又一坑 - C++标准和ABI兼容性 AI真好用-给Blog主题统一加mermaid,chart.js,excalidraw,draw.io的多种引入方式支持 给内网部署Squid-通用HTTP下载缓存 UE使用CodeChecker和clang-tidy生成静态分析报告 找出UE的循环依赖 C++小协程栈和临时变量及作用域的栈溢出问题分析 游戏服务的可观测性能力建设(C++生态) 指标上报的多线程优化和多拉取源点优化 协程(libcopp)的Channel功能和CPU命中率优化 通用RPC代码生成器 实现strong_rc_ptr(比shared_ptr更快的引用计数智能指针) 手夯一个STL allocator和对象内存分析组件 std::condition_variable 的信号丢失问题 踩坑一处(GCC)STL std::async 实现BUG导致的crash问题 GCC 14的一个warning to error BUG 给xresloader(Excel导表工具)增强UE读表支持(包含蓝图,Blueprint) Opentelemetry社区在gRPC的几个链接问题(静态库和动态库混用,musl工具链,符号裁剪) Excel转表工具(xresloader)的新验证器(验证外部Excel和文本数据,唯一性和自定义规则) protobuf v22和gRPC v1.55版本升级的依赖变化和upb适配 关于protobuf近期版本(v20/v3.20+)和 gRPC v1.54版本在某些编译环境下的一些链接和编译问题 xresloader-Excel导表工具链的近期变更汇总 打通游戏服务端框架的C++20协程改造的最后一环 Opentelemetry-cpp的Logs模块标准更新(涉及近期版本:1.8-1.9的BREAK CHANGES) 给cmake-toolset和工具链(curl等)加HTTP/2和HTTP/3支持 又开新坑之 coredns 插件: nftables和filter 关于opentelemetry-cpp社区对于C++ Head Only组件单例和符号可见性的讨论小记 填个转表工具 xresloader 去年的坑(数组尾部裁剪) 集成 upb 和 lua binding 的踩坑小记 libcopp对C++20协程的接入和接口设计 再度优化GCC、LLVM、Clang、libc++、libc++abi等套件的构建脚本 游戏服务的分布式事务优化(二)- 事务管理 游戏服务的分布式事务优化(一)- Write Ahead Log(WAL) 模块 记录一些bazel适配用编译选项 测试现代化硬件C++浮点数性能和一致性 适配Boringssl和OpenSSL 3.0 近期cmake-toolset的一些适配问题 C++20 Text Formatting/fmtlib 适配问题小记 再次重构LLVM+Clang+libcxx+libc++abi+其他相关工具的构建流程 重构基于CMake的构建工具链 新版GCC和LLVM+Clang终于Release啦 折腾一下nftables下的双拨 [C++20] Module partitions和符号交叉引用(声明和实现分离) [Rust] 实现一个线程安全且迭代器可以保存的链表 基于protobuf的代码生成 几个使用protobuf中C++接口的Arena的坑 Amazon Aurora DB存储引擎论文阅读小记 近期对libatapp的一些优化调整(增加服务发现和连接管理,支持yaml等) xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等) 在游戏服务器中使用分布式事务 libcopp接入C++20 Coroutine和一些过渡期的设计 libatbus 的大幅优化 nftables初体验 容器配置开发环境小计 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玩具-企业微信机器人通用服务 使用ELK辅助监控开发测试环境服务质量和问题定位 2018年的新通用伪随机数算法(xoshiro / xoroshiro)的C++(head only)实现 Webpack+vue+boostrap+ejs构建Web版GM工具 Rust的第二次接触-写个小服务器程序 理解和适配AEAD加密套件 atsf4g-co的进化:协程框架v2、对象路由系统和一些其他细节优化 协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比 可执行文件压缩 初识Rust 使用restructedtext编写xresloader文档 atframework的etcd模块化重构 C++的backtrace ECDH椭圆双曲线(比DH快10倍的密钥交换)算法简介和封装 protobuf-net的动态Message实现 pbc的proto3接入 atgateway内置协议流程优化-加密、算法协商和ECDH 整理一波软件源镜像同步工具+DevOps工具 Blog切换到Hugo libcopp v2的第一波优化完成 libcopp(v2) vs goroutine性能测试 GCC 7和LLVM+Clang+libc++abi 4.0的构建脚本 libatbus的几个藏得很深的bug 用cmake交叉编译到iOS和Android 开源项目得一些小维护 atapp的c binding和c#适配 对象路由系统设计 2016年总结 近期的一个协程流程BUG 重写了llvm+clang+libc++和libc++abi的构建脚本 atsf4g完整游戏工程示例|I'm OWenT atframework基本框架已经完成|I'm OWenT
libcopp的线程安全、栈池和merge boost.context 1.64.0
2017-05-13 · via I'm OWenT

blog-website

线程安全

前段时间看到了一个完成读比较高的协程库-libgo,里面提供了线程安全的协程实现,并且也是使用锁。本来我并没有给libcopp里的功能加锁的打算,因为上层dispatcher还是比较容易做到安全分发的,所以原来并不保证线程安全。而且线程安全这种问题单元测试比较难写,可能还得碰点运气。但是思来想去,还是为线程安全做点什么吧。反正也不是很复杂。

由于我并没有给utils加互斥锁的跨平台适配,所以先就直接用了自旋锁,来锁住需要考虑线程安全的地方。其实需要加锁的地方并不多,无非是管理器的增删查和task的next函数需要加锁。这些逻辑都很短,功能也很简单,并不会占用太多时间,所以自旋锁的问题也不大。而且以后真发现有问题,换掉也不是什么难事儿。

栈池和协程任务管理器

前段时间发现我的压力测试代码有问题。之前测出来使用内存池来做栈的创建开销和直接malloc或者mmap的性能差不多,所以一致没有做池子的功能。但是后来发现其实CPU耗在了缺页中断上面。于是尝试消除掉缺页中断的影响,然后发现创建性能提升了10倍。

这样的话,栈池就变得有意义了,而且由于我们新游戏是走得MMO路线,而这里异步操作比较多,也比较频繁,而且还有定时器恢复的时候也要重新进入协程。所以这个创建开销还是蛮重要的。原来一次协程创建大约需要8us,意味着QPS只有大约10W。现在缩减到300ns了,可以到300W,和一次协程切换差不太多,这样的话协程的开销就可以几乎忽略了,因为剩下的逻辑很难低于这个开销。另外栈池使用模板实现了,因为希望能够自定义池子里没有对象时的分配策略,并且能使用之前的栈分配器。所以模板的参数就是原来的分配器的类型。

boost 1.64发布了,所以顺便merge一下boost.context 1.64版本。但是这次merge的时候我看了下boost.context的汇编代码,让我对boost的代码质量开始表示怀疑了。

在merge boost.context 1.63之后,我这里libcopp的单元测试在MinGW下会崩溃。但是由于目前我这里没有在使用MinGW的环境作为开发所以并没有太在意。

然后这次的merge里我看到的CHANGELOG里有关于MinGW的修复,所以就去看了下他改了什么,结果发现其实是一个非常2B的错误(写错了一个寄存器名字)。

这种很容易发现的问题,竟然进入了Release里。这至少说明boost.context的单元测试覆盖本身就很有问题,或者说单元测试没过竟然就发布了。以后merge的时候还是得review一遍他的代码。

有一个重要的变化(虽然libcopp里并没有用到),废弃了execution_context。我之前就提到过,这个东西的设计很有诸多问题,功能和libcopp里的coroutine_container差不多,果然现在被废弃了。估计有使用这个的人或者其他库会挺伤的吧。

boost.context移除了coroutine_container,所以加了个一个更细粒度的API: callcccontinuation。这组API的实现的特点大致上如下:

  • 默认使用fixedsize_stack(posix下使用malloc,windows下使用VirtualAlloc)
  • 依赖C++11右值语义来进行资源转移
  • continuation保存fcontext
  • 栈空间的前一部分用于保存执行上下文Record(对齐到64字节),后面跟执行栈

无论是boost.context还是我的libcopp,都使用std::function作为回调委托。然而std::function底层如果绑定的是仿函数或者lambda表达式,就避免不了new操作。那么完全避免new的意义就不是很大。

所以libcopp仍然使用智能指针维护协程上下文。

性能优化

一开始看到libgo的时候,发现它比libcopp快一些。但是因为底层都是boost.context,所以按理来说不会有太大差别。起初测试的时候用的是-O2,后来发现libcopp使用-O3编译的效果,性能就和libgo(因为libgo的CI里配置的是使用-O3)接近了。即便是这样,我后来还是发现libcopp能有一些优化空间。所以这次优化先是把自己构造shared_ptr改成了make_shared,来减少内存碎片。这个优化之后,libcopp的-O2也能有libgo的-O3相近的性能了。

最近还看到另一个库call_in_stack,作者说是创建栈的时候不要对齐到4KB,这样能减少分支预测的cache miss,能大幅提高协程效率,我这里拿最简单的切换测试了一下不对齐的栈空间的切换开销大约是20ns,即便在对齐的情况下也只有80ns。十分NB,不过这些测试都是没有栈和协程管理开销的情况下,裸的切换开销。我打算过段时间也去研究下这个。术语好像叫stride access,还有一个是辅助函数返回预测的,http://en.wikipedia.org/wiki/Branch_predictor#Prediction_of_function_returns 里有描述,等有空我也要研究下。而且他这个库并没有考虑线程安全、资源管理之类的东西,还需要额外的实现。而我们游戏中使用协程其实很容易cache miss的。因为逻辑必然比协程切换要复杂,几乎必然cache miss。所以我这里的压力测试结果和libgo差不太多,比它稍微好一些。

boost.context的新API,call/cc把context存在了分配的栈里,然后一64字节对齐加了offset,不清楚对这个缓存命中和分支预测是否有影响。所以顺便测试了一下。简要的说,测试结果是:-O2优化的情况下,创建boost::context::continuation的开销大约是330ns,协程切换开销大约是60-70ns。而如果只开启一个协程栈,切换开销只要14ns。这个性能大约是call_in_stack的一半,但是兼容性要好多了。 测试代码及测试结果见: https://gist.github.com/owent/110a4f73dd8def89e98d4cd66a0b8260

接下来是libcopp profile结果:

简报: -O2情况下,使用栈池的创建开销约250ns。低cache miss协程切换约需要70ns,高cache miss需要约200ns,同时协程任务和action的维护代价约是40-50ns。也就是说如果使用完整的协程任务+栈池。那么实际场景中的切换开销约250ns。简要的profile如下:

协程上下文切换

Samples: 169K of event 'cpu-clock', Event count (approx.): 42282500000
  Children      Self  Command          Shared Object                          Symbol                                                                                          
+   68.13%    68.12%  sample_benchmar  sample_benchmark_coroutine_stack_pool  [.] copp_jump_fcontext
+   18.56%     0.00%  sample_benchmar  [unknown]                              [.] 0000000000000000
+   18.48%     0.00%  sample_benchmar  sample_benchmark_coroutine_stack_pool  [.] copp::detail::coroutine_context_container<copp::detail::coroutine_context_base, copp::allocator::stack_all
+   18.48%     0.00%  sample_benchmar  [unknown]                              [k] 0xec83485355544155
+    8.58%     8.58%  sample_benchmar  sample_benchmark_coroutine_stack_pool  [.] copp::detail::coroutine_context_base::yield
+    7.52%     7.52%  sample_benchmar  sample_benchmark_coroutine_stack_pool  [.] copp::detail::coroutine_context_base::start
+    5.32%     5.32%  sample_benchmar  sample_benchmark_coroutine_stack_pool  [.] copp::detail::coroutine_context_base::jump_to
+    4.64%     4.64%  sample_benchmar  sample_benchmark_coroutine_stack_pool  [.] main
+    3.55%     3.55%  sample_benchmar  sample_benchmark_coroutine_stack_pool  [.] my_runner::operator()
+    1.11%     1.11%  sample_benchmar  sample_benchmark_coroutine_stack_pool  [.] copp::detail::coroutine_context_base::is_finished

协程任务切换

Samples: 211K of event 'cpu-clock', Event count (approx.): 52831500000
  Children      Self  Command          Shared Object                     Symbol
+   51.24%    51.19%  sample_benchmar  sample_benchmark_task_stack_pool  [.] copp_jump_fcontext
+   15.25%     0.00%  sample_benchmar  [unknown]                         [.] 0xec83485355544155
+   15.25%     0.00%  sample_benchmar  sample_benchmark_task_stack_pool  [.] cotask::task<my_macro_coroutine, my_macro_task>::~task
+    8.14%     8.14%  sample_benchmar  sample_benchmark_task_stack_pool  [.] cotask::impl::task_impl::_cas_status
+    7.82%     7.81%  sample_benchmar  sample_benchmark_task_stack_pool  [.] copp::detail::coroutine_context_base::yield
+    6.35%     6.34%  sample_benchmar  sample_benchmark_task_stack_pool  [.] copp::detail::coroutine_context_base::jump_to
+    6.14%     6.13%  sample_benchmar  sample_benchmark_task_stack_pool  [.] copp::detail::coroutine_context_base::start
+    4.53%     4.52%  sample_benchmar  sample_benchmark_task_stack_pool  [.] cotask::task<my_macro_coroutine, my_macro_task>::start
+    4.02%     4.01%  sample_benchmar  sample_benchmark_task_stack_pool  [.] main
+    3.11%     3.06%  sample_benchmar  sample_benchmark_task_stack_pool  [.] my_task_action
+    1.61%     1.61%  sample_benchmar  sample_benchmark_task_stack_pool  [.] cotask::impl::task_impl::get_status
+    1.40%     1.40%  sample_benchmar  sample_benchmark_task_stack_pool  [.] cotask::this_task::get_task
+    1.29%     1.28%  sample_benchmar  sample_benchmark_task_stack_pool  [.] copp::this_coroutine::get_coroutine
+    0.98%     0.97%  sample_benchmar  sample_benchmark_task_stack_pool  [.] cotask::task<my_macro_coroutine, my_macro_task>::is_completed
+    0.83%     0.83%  sample_benchmar  sample_benchmark_task_stack_pool  [.] cotask::task<my_macro_coroutine, my_macro_task>::yield
+    0.77%     0.76%  sample_benchmar  sample_benchmark_task_stack_pool  [.] cotask::impl::task_impl::this_task
     0.72%     0.72%  sample_benchmar  sample_benchmark_task_stack_pool  [.] copp::detail::coroutine_context_base::is_finished
     0.51%     0.51%  sample_benchmar  sample_benchmark_task_stack_pool  [.] cotask::task<my_macro_coroutine, my_macro_task>::resume

更细节的压力测试可以见: Linux&maxOSWindows 里的运行结果,反正每次构建都会跑。

顺便一提这次的优化也和libgo学了一招,不止让CI跑单元测试了,也去跑压测。这样随时能看到性能数据。另外之前说改成Release后性能大幅提高,但是我想应该不会在乎这点性能吧,所以改回了RelWithDebInfo,毕竟万一出问题了,可以看core dump或者调试还是蛮重要的。

不过我仍然保持一个观点,就是协程库只做好协程,所以并没有在里面集成一些系统调用的钩子,比如sendwrite等。这些应该通过附加组件的形式来做,并且又不难做,只是跨平台适配恶心点。所以我这里还是追求协程本身的功能和性能。

TODO

C++1z后面可能C++会内置支持co_await之类的关键字了。我最近也在抽空看它的原理和文档。后面有时间我也会做一下这些的集成和支持的。不过这个还没那么快。

还有就是前面提到的分支预测的优化,我也需要再找点资料。再评估一下,看看有没有必要搞进去。

我思考了一下,虽然当时做了很多软件工程上的预留(比如允许共享action之类)。但是实际使用过程中,这些预留很多都没有必要,未来会考虑移除这些预留,并且和boost.context的call/cc一样直接分配在栈上,这样能进一步减少内存碎片,不过这个优化会导致结构变化和部分API向前不兼容,所以也需要再设计一下。大致的思路就是尽可能把这些数据保存在栈里,不需要额外分配,这样就减少了内存碎片,也同时减少malloc的负担。

同时有必要进一步减少原子操作的频率,因为我这里测试的原子操作的每次写大约需要7ns,也就是大约是L1 cache的失效时间。现在有一些冗余可以进一步优化的。还有更多细节的地方可以优先使用右值引用来优化性能,这也能减少内部对原子操作的消耗。而且读写操作可以进一步优化同步屏障的类型,来减少cache miss的量。

按照boost.context的call/cc的profile的结果,在协程对象创建上能够优化的量已经比较小了,但是在切换上还有比较大的优化空间,现在在有些情况下libcopp的切换效率接近boost.context的call/cc,但是并不是很稳定有些情况还是会到200ns。当然因为要保证线程安全有些开销必不可少,所以的后续再深度分析一下。同时cotask的目前的创建开销和切换开销还比较大,还有比较可观的优化空间。不过这部分的对比因为和上面的一些优化点有关,所以我还是会优先解决上面提到的那些,再来看下效果。