gpt4 book ai didi

c++ - QtConcurrent 为多核提供更长的运行时间

转载 作者:行者123 更新时间:2023-11-28 04:37:09 25 4
gpt4 key购买 nike

我已经设计了一种算法,现在我正在研究实现以在多核上解决它。基本上我给每个核心相同的问题,我会选择得分最高的解决方案。但是,我注意到使用多核会减慢代码的运行时间,但我不明白为什么。所以我创建了一个非常简单的示例来展示相同的行为。我有一个简单的 Algoritmn 类:

算法.h

 class Algorithm
{
public:
Algorithm() : mDummy(0) {};
void runAlgorithm();

protected:
long mDummy;
};

算法.cpp

    #include "algorithm.h"

void Algorithm::runAlgorithm()
{
long long k = 0;
for (long long i = 0; i < 200000; ++i)
{
for (long long j = 0; j < 200000; ++j)
{
k = k + i - j;
}
}
mDummy = k;
}

主要.cpp

    #include "algorithm.h"
#include <QtCore/QCoreApplication>
#include <QtConcurrent/QtConcurrent>

#include <vector>
#include <fstream>
#include <QFuture>
#include <memory>

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
std::ofstream logFile;
logFile.open("AlgorithmLog.log", std::ios::trunc | std::ios::out);
if (!logFile.is_open())
{
return 1;
}

for (int i = 1; i < 8; i++)
{
int cores = i;
logFile << "Start: cores = " << cores << " " << QDateTime::currentDateTime().toString(Qt::ISODate).toLatin1().data() << "\n";

std::vector<std::unique_ptr<Algorithm>> cvAlgorithmRuns;
for (int j = 0; j < cores; ++j)
cvAlgorithmRuns.push_back(std::unique_ptr<Algorithm>(new Algorithm()));

QFuture<void> assyncCalls = QtConcurrent::map(cvAlgorithmRuns, [](std::unique_ptr<Algorithm>& x) { x->runAlgorithm(); });
assyncCalls.waitForFinished();

logFile << "End: " << QDateTime::currentDateTime().toString(Qt::ISODate).toLatin1().data() << "\n";
logFile.flush();
}
logFile.close();
return a.exec();
}

当我在我的笔记本电脑上运行它时(我使用的是 VS2015、x64、Qt 5.9.0、8 个逻辑处理器)我得到:

Start: cores = 1   2018-06-28T10:48:30 End: 2018-06-28T10:48:44
Start: cores = 2 2018-06-28T10:48:44 End: 2018-06-28T10:48:58
Start: cores = 3 2018-06-28T10:48:58 End: 2018-06-28T10:49:13
Start: cores = 4 2018-06-28T10:49:13 End: 2018-06-28T10:49:28
Start: cores = 5 2018-06-28T10:49:28 End: 2018-06-28T10:49:43
Start: cores = 6 2018-06-28T10:49:43 End: 2018-06-28T10:49:58
Start: cores = 7 2018-06-28T10:49:58 End: 2018-06-28T10:50:13

这是有道理的:无论我使用 1 个核心还是 7 个核心,所有步骤的运行时间都相同(14 到 15 秒之间)。

但是当我将 algoritm.h 中的行从:

protected:
long mDummy;

到:

protected:
double mDummy;

我得到这些结果:

Start: cores = 1   2018-06-28T10:52:30 End: 2018-06-28T10:52:44
Start: cores = 2 2018-06-28T10:52:44 End: 2018-06-28T10:52:59
Start: cores = 3 2018-06-28T10:52:59 End: 2018-06-28T10:53:15
Start: cores = 4 2018-06-28T10:53:15 End: 2018-06-28T10:53:32
Start: cores = 5 2018-06-28T10:53:32 End: 2018-06-28T10:53:53
Start: cores = 6 2018-06-28T10:53:53 End: 2018-06-28T10:54:14
Start: cores = 7 2018-06-28T10:54:14 End: 2018-06-28T10:54:38

在这里,我从 1 个内核的 14 秒运行时间开始,但使用 7 个内核时运行时间增加到 24 秒。

谁能解释为什么在第二次运行时使用多核时运行时间会增加?

最佳答案

我认为问题出在@Aconcagua 建议的 FPU 的实际数量上。“逻辑处理器”又名“超线程”与拥有两倍的内核不同。

超线程中的 8 个核心仍然是 4 个“真正的”核心。如果你仔细观察你的计时,你会发现执行时间几乎相同,直到你使用超过 4 个线程。当您使用超过 4 个线程时,您可能会开始耗尽 FPU。

但是,为了更好地理解这个问题,我建议您查看生成的实际汇编 代码。

当我们想要衡量原始性能时,我们必须记住,我们的 C++ 代码只是一个更高级别的表示,实际的可执行文件可能与我们预期的完全不同。

编译器会执行优化,CPU 会乱序执行,等等......

因此,首先我会建议在您的循环中避免使用常量限制。根据具体情况,编译器可能会展开循环,甚至用其计算结果完全替换

举个例子,代码:

int main()
{
int z = 0;
for(int k=0; k < 1000; k++)
z += k;

return z;
}

由 GCC 8.1 编译并优化 -O2 为:

main:
mov eax, 499500
ret

如您所见,循环刚刚消失了!

编译器将其替换为实际的最终结果。

使用这样的示例来衡量性能是危险的。对于上面的示例,迭代 1000 次或 80000 次完全相同,因为在这两种情况下循环都被替换为常量(当然,如果您溢出了循环变量,编译器将无法再替换它)。

MSVC 没有那么激进,但除非您查看汇编代码,否则您永远不知道优化器到底做了什么。

查看生成的汇编代码的问题在于它可能非常庞大...

解决这个问题的一个简单方法是使用很棒的 compiler explorer .只需输入您的 C/C++ 代码,选择您要使用的编译器并查看结果。

现在,回到您的代码,我使用适用于 x86_64 的 MSVC2015 通过编译器资源管理器对其进行了测试。

没有优化它们的汇编代码看起来几乎相同,除了最后的内部转换为 double (cvtsi2sd)。

但是,当我们启用优化(这是在 Release模式下编译时的默认设置)时,事情开始变得有趣。

编译标志位-O2,当mDummy为long变量(32位)时产生的汇编代码为:

Algorithm::runAlgorithm, COMDAT PROC
xor r8d, r8d
mov r9d, r8d
npad 10
$LL4@runAlgorit:
mov rax, r9
mov edx, 100000 ; 000186a0H
npad 8
$LL7@runAlgorit:
dec r8
add r8, rax
add rax, -4
sub rdx, 1
jne SHORT $LL7@runAlgorit
add r9, 2
cmp r9, 400000 ; 00061a80H
jl SHORT $LL4@runAlgorit
mov DWORD PTR [rcx], r8d
ret 0
Algorithm::runAlgorithm ENDP

当 mDummy 为 float 时结束:

Algorithm::runAlgorithm, COMDAT PROC
mov QWORD PTR [rsp+8], rbx
mov QWORD PTR [rsp+16], rdi
xor r10d, r10d
xor r8d, r8d
$LL4@runAlgorit:
xor edx, edx
xor r11d, r11d
xor ebx, ebx
mov r9, r8
xor edi, edi
npad 4
$LL7@runAlgorit:
add r11, -3
add r10, r9
mov rax, r8
sub r9, 4
sub rax, rdx
dec rax
add rdi, rax
mov rax, r8
sub rax, rdx
add rax, -2
add rbx, rax
mov rax, r8
sub rax, rdx
add rdx, 4
add r11, rax
cmp rdx, 200000 ; 00030d40H
jl SHORT $LL7@runAlgorit
lea rax, QWORD PTR [r11+rbx]
inc r8
add rax, rdi
add r10, rax
cmp r8, 200000 ; 00030d40H
jl SHORT $LL4@runAlgorit
mov rbx, QWORD PTR [rsp+8]
xorps xmm0, xmm0
mov rdi, QWORD PTR [rsp+16]
cvtsi2ss xmm0, r10
movss DWORD PTR [rcx], xmm0
ret 0
Algorithm::runAlgorithm ENDP

无需详细了解这两种代码的工作原理或优化器为何在这两种情况下表现不同,我们可以清楚地看到一些差异。

特别是第二个版本(mDummy 为 float 的版本):

  • 稍微长一些
  • 使用更多寄存器
  • 更频繁地访问内存

所以除了超线程问题,第二个版本更容易产生缓存未命中,并且由于缓存是共享的,这也会影响最终的执行时间。

此外,turbo boost 等功能也可能发挥作用。您的 CPU 在承受压力时可能会降速,从而导致整体执行时间增加。

作为记录,这是打开优化后 clang 产生的结果:

Algorithm::runAlgorithm(): # @Algorithm::runAlgorithm()
mov dword ptr [rdi], 0
ret

困惑?好吧……没有人在其他地方使用 mDummy,所以 clang 决定完全删除整个东西……:)

关于c++ - QtConcurrent 为多核提供更长的运行时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51079939/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com