使用協程可以快速的優化傳統代碼,將其改造為并行處理程序。但是也有一些注意事項,例如協程函數訪問外部變量導致性能反而下降的問題。
/************************************************
* libgo sample8
************************************************
* libgo作為并發編程庫使用。
************************************************/
#include <chrono>
#include <iostream>
#include <thread>
#include <atomic>
#include "coroutine.h"
#include "win_exit.h"
using namespace std;
using namespace std::chrono;
// 大計算量的函數
atomic<int64_t> c = 0; // 使用atomic對象將極大降低(20倍降低)效率。
void foo()
{
int v = 1;
for (int i = 1; i < 2000000; ++i) {
//v *= i;
v += i;
//c += v; // 若將此句用于替換上句,則協程代碼速度變慢。
}
c += v;
}
int main()
{
// 普通的for循環做法
auto start = system_clock::now();
for (int i = 0; i < 1000; ++i)
foo();
auto end = system_clock::now();
cout << "for-loop, cost ";
cout << duration_cast<milliseconds>(end - start).count() << "ms, c=" << c << endl;
c = 0;
// 使用libgo做并行計算
start = system_clock::now();
for (int i = 0; i < 1000; ++i)
go foo;
// 創建8個線程去并行執行所有協程 (由worksteal算法自動做負載均衡)
//std::thread_group tg;
thread *ts[8];
for (int i = 0; i < 8; ++i) {
/* tg.create_thread([] {
co_sched.RunUntilNoTask();
});
*/
ts[i] = new thread([] {
co_sched.RunUntilNoTask();
});
}
for (int i = 0; i < 8; ++i) {
ts[i]->join();
}
//tg.join_all();
for (int i = 0; i < 8; ++i) {
delete ts[i];
}
end = system_clock::now();
cout << "go with coroutine, cost ";
cout << duration_cast<milliseconds>(end - start).count() << "ms, c=" << c << endl;
cout << "result zero:" << c * 0 << endl;
return 0;
}
若是使用v+=i
,匯編代碼如下:
v += i;
00D4E196 mov ecx,dword ptr [v]
00D4E199 add ecx,dword ptr [ebp-8]
00D4E19C mov dword ptr [v],ecx
若是使用c+=i
,匯編代碼如下:
c += i; // 若將此句用于替換上句,則協程代碼速度變慢。說明訪問堆棧外部對象導致協程處理效率降低。
0105E196 mov eax,dword ptr [ebp-8]
0105E199 cdq
0105E19A add eax,dword ptr [c (0125E178h)]
0105E1A0 adc edx,dword ptr ds:[125E17Ch]
0105E1A6 mov dword ptr [c (0125E178h)],eax
0105E1AB mov dword ptr ds:[125E17Ch],edx
多了內存間接尋址,而前者僅涉及到寄存器尋址。
atomic
如果采用v+=i
然后在循環外部再對c賦值c+=v
,這種情況下配合thread_group,需要對c進行多線程訪問控制嗎?
CreateFiberEx
如果將代碼中改為for(int i=0; i< 3000; ++i) go foo;
會導致libgo異常,內部原因是libgo創建Task的時候內部調用的CreateFiberEx
返回NULL。
MSDN的解釋:
The number of fibers a process can create is limited by the available virtual memory. By default, every fiber has 1 megabyte of reserved stack space. Therefore, you can create at most 2028 fibers. If you reduce the default stack size, you can create more fibers. However, your application will have better performance if you use an alternate strategy for processing requests.
所以如果是使用默認棧大小,最多只能創建2028個fiber。
但是實際使用時libgo連1500個協程都無法創建。1200個可以通過。