簡體   English   中英

為什么一個線程比調用函數更快,mingw

[英]Why is one thread faster than just calling a function, mingw

當我調用函數執行時間是6.8秒。 從線程時間調用它是3.4秒,當使用2線程1.8秒時。 無論我使用什么優化口糧保持相同。

在Visual Studio中,時間與預期的3.1,3和1.7秒相同。

#include<math.h>
#include<stdio.h>
#include<windows.h>
#include <time.h>

using namespace std;

#define N 400

float a[N][N];

struct b{
    int begin;
    int end;
};

DWORD WINAPI thread(LPVOID p)
{
    b b_t = *(b*)p;

    for(int i=0;i<N;i++)
        for(int j=b_t.begin;j<b_t.end;j++)
        {
            a[i][j] = 0;
            for(int k=0;k<i;k++)
                a[i][j]+=k*sin(j)-j*cos(k);
        }

    return (0);
}

int main()
{
    clock_t t;
    HANDLE hn[2];

    b b_t[3];

    b_t[0].begin = 0;
    b_t[0].end = N;

    b_t[1].begin = 0;
    b_t[1].end = N/2;

    b_t[2].begin = N/2;
    b_t[2].end = N;

    t = clock();
    thread(&b_t[0]);
    printf("0 - %d\n",clock()-t);

    t = clock();
    hn[0] = CreateThread ( NULL, 0, thread,  &b_t[0], 0, NULL);
    WaitForSingleObject(hn[0], INFINITE );
    printf("1 - %d\n",clock()-t);

    t = clock();
    hn[0] = CreateThread ( NULL, 0, thread,  &b_t[1], 0, NULL);
    hn[1] = CreateThread ( NULL, 0, thread,  &b_t[2], 0, NULL);
    WaitForMultipleObjects(2, hn, TRUE, INFINITE );
    printf("2 - %d\n",clock()-t);

    return 0;
}

時報:

0 - 6868
1 - 3362
2 - 1827

CPU - 酷睿2雙核T9300

操作系統 - Windows 8,64位

編譯器:mingw32-g ++。exe,gcc版本4.6.2

編輯:

試過不同的順序,相同的結果,甚至試過單獨的應用程序。 任務管理器顯示功能和1線程的CPU利用率約為50%,2線程的CPU利用率為100%

每次調用后所有元素的總和是相同的:3189909.237955

Cygwin結果:2.5,2.5和2.5秒Linux結果(pthread):3.7,3.7和2.1秒

@borisbn結果:0 - 1446 1 - 1439 2 - 721。

不同之處在於數學庫中實現sin()cos()的某些結果 - 如果用其他需要時間的東西替換對這些函數的調用,則步驟0和步驟1之間的顯着差異消失。

請注意,我看到與gcc (tdm-1) 4.6.1的區別,這是一個針對32位二進制文​​件的32位工具鏈。 優化沒有區別(這並不奇怪,因為它似乎是數學庫中的東西)。

但是,如果我使用gcc (tdm64-1) 4.6.1 64位工具鏈gcc (tdm64-1) 4.6.1構建,則不會出現差異 - 無論構建是創建32位程序(使用-m32選項)還是64位程序( -m64 )。

以下是一些示例測試運行(我對源進行了少量修改以使其與C99兼容):

  • 使用32位TDM MinGW 4.6.1編譯器:

     C:\\temp>gcc --version gcc (tdm-1) 4.6.1 C:\\temp>gcc -m32 -std=gnu99 -o test.exe test.c C:\\temp>test 0 - 4082 1 - 2439 2 - 1238 
  • 使用64位TDM 4.6.1編譯器:

     C:\\temp>gcc --version gcc (tdm64-1) 4.6.1 C:\\temp>gcc -m32 -std=gnu99 -o test.exe test.c C:\\temp>test 0 - 2506 1 - 2476 2 - 1254 C:\\temp>gcc -m64 -std=gnu99 -o test.exe test.c C:\\temp>test 0 - 3031 1 - 3031 2 - 1539 

更多信息:

32位TDM分發(gcc(tdm-1)4.6.1)通過提供的導入庫鏈接到msvcrt.dll系統DLL中的sin() / cos()實現:

c:/mingw32/bin/../lib/gcc/mingw32/4.6.1/../../../libmsvcrt.a(dcfls00599.o)
                0x004a113c                _imp__cos

雖然64位分發(gcc(tdm64-1)4.6.1)似乎沒有這樣做,而是鏈接到隨分發提供的一些靜態庫實現:

c:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/4.6.1/../../../../x86_64-w64-mingw32/lib/../lib32/libmingwex.a(lib32_libmingwex_a-cos.o)
                              C:\Users\mikeb\AppData\Local\Temp\cc3pk20i.o (cos)

更新/結論:

在調試器中通過msvcrt.dllcos()實現程序集進行一些探測后,我發現主線程與顯式創建線程的時序差異是由於FPU的精度設置到非默認設置(可能是有問題的MinGW運行時在啟動時執行此操作)。 thread()函數需要兩倍的情況下,FPU設置為64位精度( REAL10或MSVC-speak _PC_64 )。 當FPU控制字不是0x27f(默認狀態?)時, msvcrt.dll運行時將在sin()cos()函數中執行以下步驟(可能還有其他浮點函數):

  • 保存當前的FPU控制字
  • 將FPU控制字設置為0x27f(我相信可以修改此值)
  • 執行fsin / fcos操作
  • 恢復已保存的FPU控制字

如果FPU控制字已經設置為預期/期望的0x27f值,則跳過保存/恢復。 顯然,保存/恢復FPU控制字是昂貴的,因為它似乎使函數所花費的時間加倍。

您可以通過在調用thread()之前將以下行添加到main()來解決此問題:

_control87( _PC_53, _MCW_PC);   // requires <float.h>

這里不是cache matter

用戶創建的線程和主線程可能有不同的運行時庫。 您可以比較計算a[i][j]+=k*sin(j)-j*cos(k); 詳細說明(數字)i,j和k的具體值以確認差異。

原因是主線程正在進行64位浮點運算,線程正在進行53位數學運算。

你可以通過更改代碼來了解這個/修復它

...
extern "C" unsigned int _control87( unsigned int newv, unsigned int mask );

DWORD WINAPI thread(LPVOID p)
{
    printf( "_control87(): 0x%.4x\n", _control87( 0, 0 ) );
    _control87(0x00010000,0x00010000);
...

輸出將是:

c:\temp>test   
_control87(): 0x8001f
0 - 2667
_control87(): 0x9001f
1 - 2683
_control87(): 0x9001f
_control87(): 0x9001f
2 - 1373

c:\temp>mingw32-c++ --version
mingw32-c++ (GCC) 4.6.2

您可以看到0將以0x10000標志運行,但一旦設置,以與1和2相同的速度運行。如果查找_control87()函數,您將看到此值為_PC_53 flag,如果保留為零,則將精度設置為53而不是64。

出於某種原因,Mingw沒有在CreateThread()在線程創建時執行的進程初始化時將其設置為相同的值。

另一項工作是使用_set_SSE2_enable(1)打開SSE2,它將運行得更快,但可能會產生不同的結果。

c:\temp>test   
0 - 1341
1 - 1326
2 - 702

我相信默認情況下這是64位,因為所有64位處理器都支持SSE2。

正如其他人建議的那樣,更改三個測試的順序以獲得更多洞察力。 此外,你有一個多核機器的事實很好地解釋了為什么使用兩個線程做一半的工作,每個花費一半的時間。 查看您的CPU使用率監視器(Control-Shift-Escape),了解在運行時間內有多少核心被最大化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM