如何評價c++的協程庫libgo?
GitHub - yyzybb537/libgo: Go-style concurrency in C++11
下面是官方文檔的介紹libgo有以下特點:
1.提供golang一般功能強大協程,基於corontine編寫代碼,可以以同步的方式編寫簡單的代碼,同時獲得非同步的性能2.支持海量協程, 創建100萬個協程只需使用2GB物理內存 3.允許用戶自由控制協程調度點,隨時隨地變更調度線程數
4.支持多線程調度協程,極易編寫並行代碼,高效的並行調度演算法,可以有效利用多個CPU核心
5.可以讓鏈接進程序的同步的第三方庫變為非同步調用,大大提升其性能。再也不用擔心某些DB官方不提供非同步driver了,比如hiredis、mysqlclient這種客戶端驅動可以直接使用,並且可以得到不輸於非同步driver的性能6.動態鏈接和靜態鏈接全都支持,便於使用C++11的用戶靜態鏈接生成可執行文件並部署至低版本的linux系統上7.提供協程鎖(co_mutex), 定時器, channel等特性, 幫助用戶更加容易地編寫程序.8.網路性能強勁,超越boost.asio非同步模型;尤其在處理小包和多線程並行方面非常強大。看起來很不錯啊,1. 語法糖很舒服,並發編程寫起來與go也差不多2. 更重要的是hook了阻塞的api,使得可以與第三方的同步網路庫無縫兼容,不知道有沒有坑? 3. 見過不少協程的輪子,但從沒見過支持多線程調度的,我覺得這個特性很有競爭力,就是不知道調度性能如何?
等語言支持吧,控制流的東西用庫做就是邪魔外道。
去年在金山技術分享的時候,我和這位作者一起參加過。當時我是分享C++17的東西,他介紹他的libgo。
我沒有使用過libgo,因為我沒有這個需求,但是我認真聽過他的設計理念以及是如何解決的。其中包括了協程上下文切換(如使用Linux ucontext, Boost.context, Windows Fiber),以及讓我印象最深的是面對高協程數的棧內存問題,是使用static stack, segmentated stack, shared stack 抑或是 copy stack? 這在C++中有什麼限制?比如你說copy stack很好,這在C++中可以做到嗎?所以這一套思路走下來,我認為是值得學習的。
而與此同時,他問過我C++17的協程,在當時只有VC++做過很粗略的原型驗證一樣的東西出來,我說和他的還沒有辦法比。對於這個,我也認為若標準出來了,這套還是交給編譯器去搞吧,無論如何這都是一種彆扭的方式,如以前的Modern C++ Design,當C++11出來以後,很多模版的奇技淫巧都可以更好更容易的處理了。在我本機的測試結果和作者給出的不一樣。性能並沒有遠遠超越golang,而是不如。可能是用的編譯器版本比較舊?我用的是tip(1.8開發版)
------------- libgo ---------------
BenchmarkSwitch_1 1000000 594 ns/op
BenchmarkSwitch_1000 1000000 1068 ns/op
BenchmarkSwitch_10000 1000000 1142 ns/op
BenchmarkChannel_0 1000000 1409 ns/op
BenchmarkChannel_1 1000000 794 ns/op
BenchmarkChannel_10000 10000 165 ns/op
BenchmarkChannel_5000000 5000000 166 ns/op
-----------------------------------
------------- golang --------------
testing: warning: no tests to run
BenchmarkSwitch_1-4 10000000 136 ns/op
BenchmarkSwitch_1000-4 3000000 532 ns/op
BenchmarkSwitch_10000-4 2000000 656 ns/op
BenchmarkChannel_0-4 3000000 487 ns/op
BenchmarkChannel_1-4 5000000 380 ns/op
BenchmarkChannel_N-4 10000000 222 ns/op
PASS
ok _/home/reus/libgo/test/golang 12.413s [no tests to run]
------------- libgo ---------------
BenchmarkSwitch_1 1000000 575 ns/op
BenchmarkSwitch_1000 1000000 1083 ns/op
BenchmarkSwitch_10000 1000000 1146 ns/op
BenchmarkChannel_0 1000000 1375 ns/op
BenchmarkChannel_1 1000000 762 ns/op
BenchmarkChannel_10000 10000 161 ns/op
BenchmarkChannel_5000000 5000000 162 ns/op
-----------------------------------
------------- golang --------------
testing: warning: no tests to run
BenchmarkSwitch_1-4 10000000 137 ns/op
BenchmarkSwitch_1000-4 3000000 532 ns/op
BenchmarkSwitch_10000-4 2000000 660 ns/op
BenchmarkChannel_0-4 3000000 495 ns/op
BenchmarkChannel_1-4 5000000 382 ns/op
BenchmarkChannel_N-4 5000000 217 ns/op
PASS
ok _/home/reus/libgo/test/golang 11.361s [no tests to run]
------------- libgo ---------------
BenchmarkSwitch_1 1000000 566 ns/op
BenchmarkSwitch_1000 1000000 1072 ns/op
BenchmarkSwitch_10000 1000000 1152 ns/op
BenchmarkChannel_0 1000000 1369 ns/op
BenchmarkChannel_1 1000000 763 ns/op
BenchmarkChannel_10000 10000 162 ns/op
BenchmarkChannel_5000000 5000000 161 ns/op
-----------------------------------
------------- golang --------------
testing: warning: no tests to run
BenchmarkSwitch_1-4 10000000 138 ns/op
BenchmarkSwitch_1000-4 3000000 539 ns/op
BenchmarkSwitch_10000-4 2000000 667 ns/op
BenchmarkChannel_0-4 3000000 499 ns/op
BenchmarkChannel_1-4 5000000 386 ns/op
BenchmarkChannel_N-4 10000000 220 ns/op
PASS
ok _/home/reus/libgo/test/golang 12.562s [no tests to run]
-----------------------------------
&> uname -a
Linux reuspc 4.7.6-1-ARCH #1 SMP PREEMPT Fri Sep 30 19:28:42 CEST 2016 x86_64 GNU/Linux
&> go version
go version devel +33b71df Thu Oct 20 15:34:20 2016 +0000 linux/amd64
&> g++ --version
g++ (GCC) 6.2.1 20160830
------------- libgo ---------------
BenchmarkSwitch_1 1000000 216 ns/op
BenchmarkSwitch_1000 1000000 501 ns/op
BenchmarkSwitch_10000 1000000 668 ns/op
BenchmarkChannel_0 1000000 663 ns/op
BenchmarkChannel_1 1000000 413 ns/op
BenchmarkChannel_10000 10000 160 ns/op
BenchmarkChannel_5000000 5000000 167 ns/op
-----------------------------------
------------- golang --------------
testing: warning: no tests to run
BenchmarkSwitch_1-4 10000000 137 ns/op
BenchmarkSwitch_1000-4 3000000 547 ns/op
BenchmarkSwitch_10000-4 2000000 708 ns/op
BenchmarkChannel_0-4 3000000 487 ns/op
BenchmarkChannel_1-4 5000000 382 ns/op
BenchmarkChannel_N-4 10000000 235 ns/op
PASS
ok _/home/reus/libgo/test/golang 12.824s [no tests to run]
------------- libgo ---------------
BenchmarkSwitch_1 1000000 218 ns/op
BenchmarkSwitch_1000 1000000 535 ns/op
BenchmarkSwitch_10000 1000000 654 ns/op
BenchmarkChannel_0 1000000 703 ns/op
BenchmarkChannel_1 1000000 434 ns/op
BenchmarkChannel_10000 10000 159 ns/op
BenchmarkChannel_5000000 5000000 160 ns/op
-----------------------------------
------------- golang --------------
testing: warning: no tests to run
BenchmarkSwitch_1-4 10000000 136 ns/op
BenchmarkSwitch_1000-4 3000000 544 ns/op
BenchmarkSwitch_10000-4 2000000 704 ns/op
BenchmarkChannel_0-4 3000000 489 ns/op
BenchmarkChannel_1-4 5000000 383 ns/op
BenchmarkChannel_N-4 10000000 219 ns/op
PASS
ok _/home/reus/libgo/test/golang 12.649s [no tests to run]
-----------------------------------
------------- libgo ---------------
BenchmarkSwitch_1 1000000 215 ns/op
BenchmarkSwitch_1000 1000000 532 ns/op
BenchmarkSwitch_10000 1000000 652 ns/op
BenchmarkChannel_0 1000000 665 ns/op
BenchmarkChannel_1 1000000 405 ns/op
BenchmarkChannel_10000 10000 163 ns/op
BenchmarkChannel_5000000 5000000 159 ns/op
-----------------------------------
------------- golang --------------
testing: warning: no tests to run
BenchmarkSwitch_1-4 10000000 141 ns/op
BenchmarkSwitch_1000-4 3000000 532 ns/op
BenchmarkSwitch_10000-4 2000000 699 ns/op
BenchmarkChannel_0-4 3000000 488 ns/op
BenchmarkChannel_1-4 5000000 390 ns/op
BenchmarkChannel_N-4 10000000 221 ns/op
PASS
ok _/home/reus/libgo/test/golang 12.654s [no tests to run]
-----------------------------------
正確的姿勢在這裡 http://llvm.org/docs/Coroutines.html
發現我總是比人晚一步,前幾天剛造了個高仿 js Promise 的輪子,發現libgo已經發明了 await的輪子。
簡單測了下,我這輪子還不賴 --
BenchmarkSwitch_1 1000000 184 ns/opBenchmarkSwitch_1000 1000000 289 ns/op
BenchmarkSwitch_10000 1000000 471 ns/opBenchmarkSwitch_100000 1000000 662 ns/op代碼在 test est_tasks.cpp。(編譯時,先安裝codelite)GitHub - xhawk18/promise-cpp: Promise API implemented by cpp as Javascript promise style.測試環境 --
Linux xh-Lenovo-G470 4.4.0-22-generic #40-Ubuntu SMP Thu May 12 22:03:46 UTC 2016 x86_64 x86_64 x86_64 GNU/LinuxPromise鏈式then調用,不需要有協程,速度快佔資源小。
缺點就是寫進循環里,還是比較麻煩的。(存心蹭了個熱點,逃。。。)
================================================
這幾天又更新了一下,讓c++ promise庫在實用性上增強不少 --1. resolve和reject可以發送任意多個參數,和js一樣。2. throw出的對象,可以通過 fail函數,或then的第二個lambda函數抓到。3. 類型全自動推導。雖然是個模板庫,用戶代碼里,幾乎不需寫尖括弧&<&>的模板類型代碼。4. promise對象在內置內存池上分配,效率飛快。如果喜歡協程那種同步原語,我覺得應該C++ + Lua。開發語言使用Lua(協程)。
譬如這個:Joynet/TestHttpServer.lua at master · IronsDu/Joynet · GitHub
C++作為開發語言的話,現在還是用最正確的非同步模式吧。作為libgo的作者,好壞不便於評價,至少我們項目組用的都挺爽的~
在此展示一些設計理念和測試結果吧.
Q: 更重要的是hook了阻塞的api,使得可以與第三方的同步網路庫無縫兼容,不知道有沒有坑?
A: libgo在hook的時候,會讓api的行為與系統原生api完全保持一致,每一個邊界條件的返回值和錯誤碼都是一致的。在魅族推送系統線上的100多台伺服器中經過了半年左右的考驗,目前最新的2.6版本已經非常穩定了。Q: 見過不少協程的輪子,但從沒見過支持多線程調度的,我覺得這個特性很有競爭力,就是不知道調度性能如何?
A: 多線程調度使用的是worksteal演算法,性能上的損耗很小,遠遠快於golang。以下是libgo協程切換速度和管道讀寫速度與golang對比測試的結果:
------------- libgo ---------------BenchmarkSwitch_1 1000000 154 ns/opBenchmarkSwitch_1000 1000000 105 ns/opBenchmarkSwitch_10000 1000000 553 ns/opBenchmarkChannel_0 1000000 485 ns/op
BenchmarkChannel_1 1000000 299 ns/opBenchmarkChannel_10000 10000 108 ns/opBenchmarkChannel_5000000 5000000 113 ns/op--------------------------------------------------- golang --------------BenchmarkSwitch_1 1000000 1438 ns/opBenchmarkSwitch_1000 1000000 1654 ns/opBenchmarkSwitch_10000 500000 2375 ns/opBenchmarkChannel_0 1000000 1536 ns/opBenchmarkChannel_1 500000 3037 ns/op
BenchmarkChannel_N 50000000 65.0 ns/op--------------------------------------測試代碼在libgo/test/golang目錄中,直接執行test.sh腳本即可得到結果.
下面有熱心用戶給出的另一組結果截然不同的測試數據,在此補充一點,libgo使用boost1.59以上的版本才可以獲得這樣的高性能,cmake命令執行參數:$ cmake .. -DENABLE_BOOST_CONTEXT=ON
管道性能比golang低不少。其它方面還好。
使用xhook,讓第三方的同步庫變非同步庫的特性慎用,特別是在一個項目里引入各種第三方庫的時候。坑很多。
還是讓標準支持一下協程吧。
vs2015 resumefunction 就不錯,還是等語言支持吧,寫代碼確實可以起飛了。
我認知內最好的協程庫,state-threads 。
推薦閱讀:
※為什麼一般要用#include「xxx.h」?
※模塊相比於 #include 指令的優點是什麼?
※C++為什麼允許s1+s2="D";這種語句?
※什麼時候應當依靠返回值優化(RVO)?