使用协程可以快速的优化传统代码,将其改造为并行处理程序。但是也有一些注意事项,例如协程函数访问外部变量导致性能反而下降的问题。
/************************************************
* 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个可以通过。