libgo sample8體驗

使用協程可以快速的優化傳統代碼,將其改造為并行處理程序。但是也有一些注意事項,例如協程函數訪問外部變量導致性能反而下降的問題。

/************************************************
 * 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個可以通過。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容