【问题标题】:Why is one thread faster than just calling a function, mingw为什么一个线程比仅仅调用一个函数要快,mingw
【发布时间】:2012-12-29 01:09:59
【问题描述】:

当我调用函数时,执行时间是 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 个线程的 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。

【问题讨论】:

  • 我能感觉到神秘的打字......
  • @SethCarnegie Nah... 我无法一眼发现问题,而且我没有安装 mingw32。
  • 是的,尝试不同的顺序,看看是否会有所不同。我敢打赌“第一次”需要更长的时间,而不是线程与无线程。
  • MinGW 4.6.2。正是你的代码。编译选项:-O2。结果:0 - 1446 1 - 1439 2 - 721std::thread 的结果相同。我试图在函数调用之前运行线程,我将float a[N][N]; 移动到struct b 中 - 结果没有改变
  • @user1978768 这是一个 exe 文件,由我的 mingw 从您的代码编译 - dl.dropbox.com/u/46469564/a.exe

标签: c++ multithreading performance function mingw


【解决方案1】:

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

【讨论】:

  • 尝试了不同的顺序,相同的结果,甚至尝试了不同的应用程序。任务管理器显示函数和 1 个线程的 CPU 利用率约为 50%,2 个线程的 CPU 利用率为 100%
【解决方案2】:

这里不是cache matter

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

【讨论】:

  • 每次调用后所有元素的总和都是一样的:3189909.237955
  • 请。检查 RTL 选择 here 并验证您使用的是哪一个。 MinGW 定义了MSVCRT_VERSION"
  • # 定义 MSVCRT_VERSION 0x0600
  • 看到您接受了告诉您遇到库问题的答案,这是我最初的想法,我建议您使用 -D__MSVCRT_VERSION__=0x0601 进行编译,因为您运行的是 Windows 8
【解决方案3】:

差异是数学库中实现 sin()cos() 的结果 - 如果您用其他需要时间的方法替换对这些函数的调用,那么 step 和 0 和 step 1 之间的显着差异就会消失。

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

但是,如果我使用 gcc (tdm64-1) 4.6.1(这是一个 64 位工具链)进行构建,则不会出现差异 - 无论构建是否正在创建 32 位程序(使用 @987654325 @option) 或 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) 通过提供的导入库链接到 sin()/cos() 系统 DLL 中的 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 中的_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>

【讨论】:

  • 答案已更新,更准确地解决了问题。
  • +1 效果很好。但是,MinGW 设置这些奇怪的设置的目的是什么,让“主线程”和“用户创建的线程”具有不同的 FPU 精度?
  • @Arno:我不知道为什么某些 MinGW 版本将 FP 精度设置为 64 位。至于创建线程的精度设置 - 这是在创建线程时不“继承” FP 精度的 Windows 细节 - CreateThread() 中不涉及 MinGW 的库。在我看来,msvcrt.dll 中的一个错误是在执行fcos/fsin 操作时降低精度(我想安全/修改/恢复精度逻辑旨在使确保操作没有在 FPU 设置为单精度的情况下完成)。
【解决方案4】:

原因是主线程在做 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 标志,它将精度设置为 53 而不是 64(如果它保留为零)。

由于某种原因,Mingw 在进程初始化时将其设置为与 CreateThread() 在线程创建时所做的值不同。

另一种解决方法是使用_set_SSE2_enable(1) 打开 SSE2,这将运行得更快,但可能会产生不同的结果。

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

我相信这对于 64 位是默认开启的,因为所有 64 位处理器都支持 SSE2。

【讨论】:

    猜你喜欢
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多