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

推荐订阅源

L
LangChain Blog
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
MyScale Blog
MyScale Blog
P
Proofpoint News Feed
S
Security @ Cisco Blogs
N
News and Events Feed by Topic
H
Hacker News: Front Page
Attack and Defense Labs
Attack and Defense Labs
S
Secure Thoughts
Microsoft Security Blog
Microsoft Security Blog
N
Netflix TechBlog - Medium
U
Unit 42
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
Google Online Security Blog
Google Online Security Blog
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
L
LINUX DO - 热门话题
T
Tenable Blog
博客园 - 叶小钗
D
DataBreaches.Net
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
人人都是产品经理
人人都是产品经理
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
量子位
P
Proofpoint News Feed
H
Help Net Security
Blog — PlanetScale
Blog — PlanetScale
宝玉的分享
宝玉的分享
Recorded Future
Recorded Future
The Register - Security
The Register - Security
F
Fortinet All Blogs
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
S
Schneier on Security
V
Vulnerabilities – Threatpost
雷峰网
雷峰网
Microsoft Azure Blog
Microsoft Azure Blog
G
GRAHAM CLULEY
G
Google Developers Blog
月光博客
月光博客
V
V2EX
T
Troy Hunt's Blog
A
Arctic Wolf

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的线程安全、栈池和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 重写了llvm+clang+libc++和libc++abi的构建脚本 atsf4g完整游戏工程示例|I'm OWenT atframework基本框架已经完成|I'm OWenT
libcopp(v2) vs goroutine性能测试
2017-06-10 · via I'm OWenT

blog-website

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

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

我结合valgrind和perf的报告分析了一下原因,原来的读L1 cache miss大约在68%左右,而v2里的读L1 cache miss到了98%。究其原因,原来的协程、协程任务对象和协程任务的actor是由malloc分配的,而现在在v2里全部优化后放进了分配的执行栈里。这样减少了三次malloc操作。但是这也导致不同协程、任务和actor之间的距离隔得非常远,必超出L1 cache的量(一般是64字节)。那么就必然容易L1 cache miss了。而原来在benchmark里由于是连续分配的,所以他们互相都在比较近的位置,当然原来的性能高了。

这种情况,因该说是原来的benchmark更加不能作为实际使用过程中的性能参考依据。之前也说过,因为在实际应用场景中几乎必然cache miss,因为逻辑会更复杂得多,内存访问也更切换得频繁和多。

这让我突然想到了go语言的goroutine。不知道这玩意有没有考虑切换时的Cache miss开销,不知道针对这个做优化。因为它是语言级别实现的go程,那么如果要针对缓存做优化则比较容易实现一些。不过它的动态栈总归会有一定的开销。

这里还是用了和libcopp里差不多的测试方法。benchmark的代码如下:

package main

import (
	"fmt"
	"os"
	"strconv"
	"time"
)

func runCallback(in, out chan int64) {
	for n, ok := <-in; ok; n, ok = <-in {
		out <- n
	}
}

func runTest(round int, coroutineNum, switchTimes int64) {
	fmt.Printf("##### Round: %v\n", round)
	start := time.Now()
	channelsIn, channelsOut := make([]chan int64, coroutineNum), make([]chan int64, coroutineNum)
	for i := int64(0); i < coroutineNum; i++ {
		channelsIn[i] = make(chan int64, 1)
		channelsOut[i] = make(chan int64, 1)
	}
	end := time.Now()
	fmt.Printf("Create %v goroutines and channels cost %vns, avg %vns\n", coroutineNum, end.Sub(start).Nanoseconds(), end.Sub(start).Nanoseconds()/coroutineNum)

	start = time.Now()
	for i := int64(0); i < coroutineNum; i++ {
		go runCallback(channelsIn[i], channelsOut[i])
	}
	end = time.Now()
	fmt.Printf("Start %v goroutines and channels cost %vns, avg %vns\n", coroutineNum, end.Sub(start).Nanoseconds(), end.Sub(start).Nanoseconds()/coroutineNum)

	var sum int64 = 0
	start = time.Now()
	for i := int64(0); i < switchTimes; i++ {
		for j := int64(0); j < coroutineNum; j++ {
			channelsIn[j] <- 1
			sum += <-channelsOut[j]
		}
	}
	end = time.Now()
	fmt.Printf("Switch %v goroutines for %v times cost %vns, avg %vns\n", coroutineNum, sum, end.Sub(start).Nanoseconds(), end.Sub(start).Nanoseconds()/sum)

	start = time.Now()
	for i := int64(0); i < coroutineNum; i++ {
		close(channelsIn[i])
		close(channelsOut[i])
	}
	end = time.Now()
	fmt.Printf("Close %v goroutines cost %vns, avg %vns\n", coroutineNum, end.Sub(start).Nanoseconds(), end.Sub(start).Nanoseconds()/coroutineNum)
}

func main() {
	var coroutineNum, switchTimes int64 = 30000, 1000

	fmt.Printf("### Run: ")
	for _, v := range os.Args {
		fmt.Printf(" \"%s\"", v)
	}
	fmt.Printf("\n")

	if (len(os.Args)) > 1 {
		v, _ := strconv.Atoi(os.Args[1])
		coroutineNum = int64(v)
	}

	if (len(os.Args)) > 2 {
		v, _ := strconv.Atoi(os.Args[2])
		switchTimes = int64(v)
	}

	for i := 1; i <= 5; i++ {
		runTest(i, coroutineNum, switchTimes)
	}
}

同时发布在了: https://gist.github.com/owent/2286768f2586521600c9fd1700cbf845

测试结果如下:

PS D:\projs\test\go> .\test_goroutine.exe
### Run:  "D:\projs\test\go\test_goroutine.exe"
##### Round: 1
Create 30000 goroutines and channels cost 6515200ns, avg 217ns
Start 30000 goroutines and channels cost 79505000ns, avg 2650ns
Switch 30000 goroutines for 30000000 times cost 42225426300ns, avg 1407ns
Close 30000 goroutines cost 15017500ns, avg 500ns
##### Round: 2
Create 30000 goroutines and channels cost 19868200ns, avg 662ns
Start 30000 goroutines and channels cost 22487700ns, avg 749ns
Switch 30000 goroutines for 30000000 times cost 44709165100ns, avg 1490ns
Close 30000 goroutines cost 15559000ns, avg 518ns
##### Round: 3
Create 30000 goroutines and channels cost 3999700ns, avg 133ns
Start 30000 goroutines and channels cost 17508400ns, avg 583ns
Switch 30000 goroutines for 30000000 times cost 50535999000ns, avg 1684ns
Close 30000 goroutines cost 36289900ns, avg 1209ns
##### Round: 4
Create 30000 goroutines and channels cost 5999600ns, avg 199ns
Start 30000 goroutines and channels cost 44500300ns, avg 1483ns
Switch 30000 goroutines for 30000000 times cost 45678842800ns, avg 1522ns
Close 30000 goroutines cost 13005600ns, avg 433ns
##### Round: 5
Create 30000 goroutines and channels cost 5000000ns, avg 166ns
Start 30000 goroutines and channels cost 14001000ns, avg 466ns
Switch 30000 goroutines for 30000000 times cost 47485810100ns, avg 1582ns
Close 30000 goroutines cost 17999800ns, avg 599ns

这里都是在我家里的Windows机器下跑的结果,在Linux下应该性能能够更好一些,因为我家里的机器比较渣,并且libcopp在Linux下性能就比在Windows下好得多。那么为了对比,还需要同样在这台机器下,同样环境的libcopp的测试结果。

这里用的是go语言推荐的协程间共享数据的方式,应该是最贴近libcopp的流程了。这里面可以看出来创建chan需要的开销并不大,但是其实goroutine的切换开销还是蛮大的,基本上都要超过1us。而且感觉go语言内部还是维护了goroutine的池子,不然创建开销抖动不会那么大。

不过goroutine的内存开销确实小,30000个goroutine的内存占用才300MB。

libcopp的同环境报告和对比

我只贴一样的协程数量和切换次数的结果了

  ###################### task (stack using stack pool) ###################
  ########## Cmd: .\sample_benchmark_task_stack_pool.exe 30000 1000 64
  ### Round: 1 ###
  create 30000 task, cost time: 0 s, clock time: 104 ms, avg: 3466 ns
  switch 30000 tasks 30000000 times, cost time: 18 s, clock time: 18500 ms, avg: 616 ns
  remove 30000 tasks, cost time: 0 s, clock time: 28 ms, avg: 933 ns
  ### Round: 2 ###
  create 30000 task, cost time: 0 s, clock time: 44 ms, avg: 1466 ns
  switch 30000 tasks 30000000 times, cost time: 19 s, clock time: 18341 ms, avg: 611 ns
  remove 30000 tasks, cost time: 0 s, clock time: 29 ms, avg: 966 ns
  ### Round: 3 ###
  create 30000 task, cost time: 0 s, clock time: 44 ms, avg: 1466 ns
  switch 30000 tasks 30000000 times, cost time: 18 s, clock time: 18188 ms, avg: 606 ns
  remove 30000 tasks, cost time: 0 s, clock time: 28 ms, avg: 933 ns
  ### Round: 4 ###
  create 30000 task, cost time: 0 s, clock time: 44 ms, avg: 1466 ns
  switch 30000 tasks 30000000 times, cost time: 18 s, clock time: 18267 ms, avg: 608 ns
  remove 30000 tasks, cost time: 0 s, clock time: 28 ms, avg: 933 ns
  ### Round: 5 ###
  create 30000 task, cost time: 0 s, clock time: 44 ms, avg: 1466 ns
  switch 30000 tasks 30000000 times, cost time: 19 s, clock time: 18772 ms, avg: 625 ns
  remove 30000 tasks, cost time: 0 s, clock time: 26 ms, avg: 866 ns

同样对比下,libcopp的切换开销就小的多了,而且比较稳定,但是创建开销也是比较大,特别是第一次要分配栈的情况下(后面都会使用栈池机制,减少系统调用所以会小很多)。

其实这是v2的测试数据,虽然切换开销比原来是要大一些,但是之前在Linux上的结果,这个创建开销已经是原来版本的一半了(Linux上的创建开销原先大约是1us,v2大约是500ns,切换开销忘记了,v2版本大约是300-400ns)。

结论

go语言现在很火了,性能超过goroutine的话肯定是已经有实用价值了,特别是逻辑开销很容易就能抹平这个协程的开销。而且go语言本来还就是目标于高性能分布式系统的,并且很多这种分布式系统的一些逻辑可能并不特别重,都能容忍这个开销,何况libcopp呢。但是libcopp的v2版本细节上仍然还有一些优化点,比如内存布局和原子操作必导致L1 Cache失效的问题等等。等我一并处理完再merge回master。现在还是放在https://github.com/owent/libcopp的v2分支里。

当然哪位大神有更好的建议希望能够不吝赐教。之前针对缓存优化的点,其实优化好了跑分会很好看,但是实用性上还得分场景。像libcopp的定位是比较重量级的场景,能够覆盖比较完整而且复杂的协程流程和逻辑,对于比较复杂的场景(比如我们游戏里),那些缓存优化就没太大意义。而对于那些简单的只是用来临时做上下文切换而且不要求跨平台跨编译器的,我还是建议使用类似call_in_stack这种轻量级的库,毕竟性能搞了一个数量级。