为什么一个线程比调用函数更快,mingw

时间:2013-01-15 00:22:34

标签: c++ multithreading performance 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 - Core 2 Duo T9300

操作系统 - Windows 8,64位

编译器:mingw32-g ++。exe,gcc版本4.6.2

编辑:

尝试了不同的顺序,相同的结果,甚至尝试了单独的应用程序。 任务管理器显示功能和1线程的CPU利用率约为50%,2线程为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。

4 个答案:

答案 0 :(得分:6)

差异是数学库中实现sin()cos()的某些结果 - 如果用其他需要时间的东西替换对这些函数的调用,则步骤0和步骤之间的显着差异1消失了。

请注意,我看到与gcc (tdm-1) 4.6.1的区别,gcc (tdm64-1) 4.6.1是一个针对32位二进制文​​件的32位工具链。优化没有区别(这并不奇怪,因为它似乎是数学库中的东西)。

但是,如果我使用-m32构建,这是一个64位工具链,那么 not 就会出现差异 - 无论构建是否正在创建一个32位程序(使用-m64选项)或64位程序(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 )。

以下是一些示例测试运行(我对源进行了少量修改以使其与C99兼容):

  • 使用32位TDM MinGW 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
    
  • 使用64位TDM 4.6.1编译器:

    sin()

更多信息:

32位TDM分发(gcc(tdm-1)4.6.1)通过提供的导入库链接到cos()系统DLL中的msvcrt.dll / c:/mingw32/bin/../lib/gcc/mingw32/4.6.1/../../../libmsvcrt.a(dcfls00599.o) 0x004a113c _imp__cos 实现:

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)

虽然64位分发(gcc(tdm64-1)4.6.1)似乎没有这样做,而是链接到随分发提供的一些静态库实现:

msvcrt.dll

<强>更新/结论:

在调试器中通过cos()的{​​{1}}实现的程序集进行了一些探索后,我发现主线程与显式创建的线程的时序差异是由于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控制字是昂贵的,因为它似乎使函数所花费的时间加倍。

您可以在致电main()之前将以下行添加到thread()来解决问题:

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

答案 1 :(得分:2)

这里不是cache matter

用户创建的线程和主线程的可能不同的运行时库。 您可以比较i,j和k的具体值的详细计算a[i][j]+=k*sin(j)-j*cos(k);以确认差异。

答案 2 :(得分:2)

原因是主线程正在进行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&amp; 1相同。 2.如果你查找_control87()函数,你会看到这个值是_PC_53标志,它将精度设置为53而不是64,如果它保留为零。

出于某种原因,Mingw没有在CreateThread()在线程创建时执行的进程初始化时将其设置为相同的值。

使用_set_SSE2_enable(1)打开SSE2的另一项工作是,它会运行得更快,但可能会产生不同的结果。

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

我认为默认情况下这是64位,因为所有64位处理器都支持SSE2。

答案 3 :(得分:0)

正如其他人所建议的那样,更改三个测试的顺序以获得更多洞察力。此外,你有一个多核机器的事实很好地解释了为什么使用两个线程做一半的工作,每个花费一半的时间。查看您的CPU使用率监视器(Control-Shift-Escape),了解在运行时间内有多少核心被最大化。