C++ 循环性能和多线程性能问题
我有点无聊,所以我想尝试使用std::thread,并最终测量单线程和多线程控制台应用程序的性能。这是一个由两部分组成的问题。所以我从一个巨大的整数向量(800000整数)的单线程和开始 此时,控制台输出如下所示:C++ 循环性能和多线程性能问题,c++,multithreading,c++11,for-loop,C++,Multithreading,C++11,For Loop,我有点无聊,所以我想尝试使用std::thread,并最终测量单线程和多线程控制台应用程序的性能。这是一个由两部分组成的问题。所以我从一个巨大的整数向量(800000整数)的单线程和开始 此时,控制台输出如下所示: index loop: 30.0017ms range loop: 221.013ms iterator loop: 442.025ms 这是一个调试版本,所以我改为发行版,与基于索引的版本相比,差异约为1ms。没什么大不了的,只是出于好奇:这三个for循环在调试模式上应该有这么大
index loop: 30.0017ms
range loop: 221.013ms
iterator loop: 442.025ms
这是一个调试版本,所以我改为发行版,与基于索引的版本相比,差异约为1ms。没什么大不了的,只是出于好奇:这三个for循环在调试模式上应该有这么大的区别吗?或者在释放模式下1毫秒内有差异
我继续创建线程,并尝试使用这个lambda对数组进行并行求和(通过引用捕获所有内容,以便我可以使用前面声明的int向量和互斥量),使用基于索引的for
auto func = [&](int start, int total, int index)
{
int partial_sum = 0;
auto s = chrono::high_resolution_clock::now();
for (int i = start; i < start + total; ++i)
partial_sum += ints[i];
auto e = chrono::high_resolution_clock::now();
auto d = e - s;
m.lock();
cout << "thread " + to_string(index) + ": " << chrono::duration<double, milli>(d).count() << "ms" << endl;
sum += partial_sum;
m.unlock();
};
for (int i = 0; i < 8; ++i)
threads.push_back(thread(func, i * 100000, 100000, i));
我猜问题的第二部分是这里发生了什么?含2个螺纹的溶液也以约30ms结束。乒乓球?还有别的吗?如果我做错了什么,正确的方法是什么?如果相关的话,我在一个有8个线程的i7上尝试了这个,所以我知道我没有计算主线程,但是用7个单独的线程尝试了它,得到了几乎相同的结果
编辑:很抱歉,忘记了在Windows7上使用VisualStudio2013和VisualStudio的v120编译器或其他名称时提到过这一点
编辑2:以下是整个主要功能:
在未启用优化的情况下,所有在幕后执行的方法调用都可能是真实的方法调用。内联函数可能不是内联的,而是真正调用的。对于模板代码,您确实需要启用优化,以避免所有代码都被逐字逐句地理解。例如,您的迭代器代码可能会调用iter.end()800000次,运算符!=对于比较800000次,它调用运算符==以此类推
对于多线程代码,处理器是复杂的。操作系统是复杂的。你的代码在计算机上并不孤单。您的计算机可以更改其时钟速度,更改为涡轮模式,更改为热防护模式。而将时间舍入到毫秒并没有真正的帮助。可能是一个线程到6.49毫秒,另一个线程到6.51毫秒,取整方式不同 由于不知道这些std::thread类是如何实现的,53ms的一个可能解释是: 线程在实例化时立即启动。(我没有看到thread.start()或threads.StartAll()或类似的线程)。因此,在第一个线程实例处于活动状态期间,主线程可能会(也可能不会)被抢占。毕竟,不能保证线程是在单个内核上生成的(线程亲和性) 如果您仔细查看POSIXAPI,就会发现“应用程序上下文”和“系统上下文”的概念,这基本上意味着可能存在一个操作系统策略,该策略不会将所有内核用于一个应用程序 在Windows上(这是您测试的地方),可能线程不是直接生成的,而是通过线程池生成的,可能带有一些额外的std::thread功能,这可能会产生开销/延迟。(如完成端口等) 不幸的是,我的机器速度非常快,所以我不得不增加处理的数据量,以获得显著的时间。但从好的方面来看,这提醒我指出,通常情况下,当计算时间远远超过一个时间片的时间时(经验法则),并行计算就开始有回报了 这里是我的“本机”Windows实现,对于足够大的数组,它最终使线程战胜了单线程计算
#include <stdafx.h>
#include <nativethreadTest.h>
#include <vector>
#include <cstdint>
#include <Windows.h>
#include <chrono>
#include <iostream>
#include <thread>
struct Range
{
Range( const int32_t *p, size_t l)
: data(p)
, length(l)
, result(0)
{}
const int32_t *data;
size_t length;
int32_t result;
};
static int32_t Sum(const int32_t * data, size_t length)
{
int32_t sum = 0;
const int32_t *end = data + length;
for (; data != end; data++)
{
sum += *data;
}
return sum;
}
static int32_t TestSingleThreaded(const Range& range)
{
return Sum(range.data, range.length);
}
DWORD
WINAPI
CalcThread
(_In_ LPVOID lpParameter
)
{
Range * myRange = reinterpret_cast<Range*>(lpParameter);
myRange->result = Sum(myRange->data, myRange->length);
return 0;
}
static int32_t TestWithNCores(const Range& range, size_t ncores)
{
int32_t result = 0;
std::vector<Range> ranges;
size_t nextStart = 0;
size_t chunkLength = range.length / ncores;
size_t remainder = range.length - chunkLength * ncores;
while (nextStart < range.length)
{
ranges.push_back(Range(&range.data[nextStart], chunkLength));
nextStart += chunkLength;
}
Range remainderRange(&range.data[range.length - remainder], remainder);
std::vector<HANDLE> threadHandles;
threadHandles.reserve(ncores);
for (size_t i = 0; i < ncores; ++i)
{
threadHandles.push_back(::CreateThread(NULL, 0, CalcThread, &ranges[i], 0, NULL));
}
int32_t remainderResult = Sum(remainderRange.data, remainderRange.length);
DWORD waitResult = ::WaitForMultipleObjects((DWORD)threadHandles.size(), &threadHandles[0], TRUE, INFINITE);
if (WAIT_OBJECT_0 == waitResult)
{
for (auto& r : ranges)
{
result += r.result;
}
result += remainderResult;
}
else
{
throw std::runtime_error("Something went horribly - HORRIBLY wrong!");
}
for (auto& h : threadHandles)
{
::CloseHandle(h);
}
return result;
}
static int32_t TestWithSTLThreads(const Range& range, size_t ncores)
{
int32_t result = 0;
std::vector<Range> ranges;
size_t nextStart = 0;
size_t chunkLength = range.length / ncores;
size_t remainder = range.length - chunkLength * ncores;
while (nextStart < range.length)
{
ranges.push_back(Range(&range.data[nextStart], chunkLength));
nextStart += chunkLength;
}
Range remainderRange(&range.data[range.length - remainder], remainder);
std::vector<std::thread> threads;
for (size_t i = 0; i < ncores; ++i)
{
threads.push_back(std::thread([](Range* range){ range->result = Sum(range->data, range->length); }, &ranges[i]));
}
int32_t remainderResult = Sum(remainderRange.data, remainderRange.length);
for (auto& t : threads)
{
t.join();
}
for (auto& r : ranges)
{
result += r.result;
}
result += remainderResult;
return result;
}
void TestNativeThreads()
{
const size_t DATA_SIZE = 800000000ULL;
typedef std::vector<int32_t> DataVector;
DataVector data;
data.reserve(DATA_SIZE);
for (size_t i = 0; i < DATA_SIZE; ++i)
{
data.push_back(static_cast<int32_t>(i));
}
Range r = { data.data(), data.size() };
std::chrono::system_clock::time_point singleThreadedStart = std::chrono::high_resolution_clock::now();
int32_t result = TestSingleThreaded(r);
std::chrono::system_clock::time_point singleThreadedEnd = std::chrono::high_resolution_clock::now();
std::cout
<< "Single threaded sum: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(singleThreadedEnd - singleThreadedStart).count()
<< "ms." << " Result = " << result << std::endl;
std::chrono::system_clock::time_point multiThreadedStart = std::chrono::high_resolution_clock::now();
result = TestWithNCores(r, 8);
std::chrono::system_clock::time_point multiThreadedEnd = std::chrono::high_resolution_clock::now();
std::cout
<< "Multi threaded sum: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(multiThreadedEnd - multiThreadedStart).count()
<< "ms." << " Result = " << result << std::endl;
std::chrono::system_clock::time_point stdThreadedStart = std::chrono::high_resolution_clock::now();
result = TestWithSTLThreads(r, 8);
std::chrono::system_clock::time_point stdThreadedEnd = std::chrono::high_resolution_clock::now();
std::cout
<< "std::thread sum: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(stdThreadedEnd - stdThreadedStart).count()
<< "ms." << " Result = " << result << std::endl;
}
最后同样重要的一点是,我觉得有必要提到,这段代码的编写方式与其说是一个核心CPU计算基准,不如说是一个内存IO性能基准。
更好的计算基准会使用少量的本地数据,适合CPU缓存等
也许将数据分成不同的范围进行实验会很有趣。如果每个线程从开始到结束都在数据上“跳跃”,并且有一个NCore的间隙,该怎么办?线程1:0 8 16。。。线程2:1917。。。等也许那时内存的“局部性”可以获得额外的速度
这三个for循环在调试模式上应该有这么大的区别吗
对。如果允许,一个好的编译器可以为3个不同的循环中的每一个生成相同的输出,但是如果没有启用优化,迭代器版本会有更多的函数调用,函数调用会有一定的开销
或者在释放模式下1毫秒内有差异
您的测试代码:
start = ...
for (auto& val : ints)
sum += val;
end = ...
diff = end - start;
sum = 0;
根本不使用循环的结果,因此在优化时,编译器应该简单地选择丢弃代码,从而产生如下结果:
start = ...
// do nothing...
end = ...
diff = end - start;
为了你所有的循环
1ms的差异可能由标准库所用实现中“
高分辨率时钟
”的高粒度以及执行过程中进程调度的差异产生。我测量的基于索引的速度慢了0.04毫秒,但这一结果毫无意义。除了在Windows上如何实现std::thread之外,我想提醒您注意可用的执行单元和上下文切换
i7没有8个实际执行单元。它是一个具有超线程的四核处理器。而且HT不会神奇地将可用线程数增加一倍,无论它是如何宣传的。这是一个非常聪明的系统,只要有可能,它会尝试从额外的管道中插入指令。但最终所有指令只经过四个执行单元。
因此,运行8(或7)个线程仍然超出了您的CPU真正能够同时处理的范围。这意味着您的CPU必须在争抢计算时间的8个热线程之间切换很多。除此之外,操作系统还提供了数百个线程,其中大多数线程都处于休眠状态
#include <stdafx.h>
#include <nativethreadTest.h>
#include <vector>
#include <cstdint>
#include <Windows.h>
#include <chrono>
#include <iostream>
#include <thread>
struct Range
{
Range( const int32_t *p, size_t l)
: data(p)
, length(l)
, result(0)
{}
const int32_t *data;
size_t length;
int32_t result;
};
static int32_t Sum(const int32_t * data, size_t length)
{
int32_t sum = 0;
const int32_t *end = data + length;
for (; data != end; data++)
{
sum += *data;
}
return sum;
}
static int32_t TestSingleThreaded(const Range& range)
{
return Sum(range.data, range.length);
}
DWORD
WINAPI
CalcThread
(_In_ LPVOID lpParameter
)
{
Range * myRange = reinterpret_cast<Range*>(lpParameter);
myRange->result = Sum(myRange->data, myRange->length);
return 0;
}
static int32_t TestWithNCores(const Range& range, size_t ncores)
{
int32_t result = 0;
std::vector<Range> ranges;
size_t nextStart = 0;
size_t chunkLength = range.length / ncores;
size_t remainder = range.length - chunkLength * ncores;
while (nextStart < range.length)
{
ranges.push_back(Range(&range.data[nextStart], chunkLength));
nextStart += chunkLength;
}
Range remainderRange(&range.data[range.length - remainder], remainder);
std::vector<HANDLE> threadHandles;
threadHandles.reserve(ncores);
for (size_t i = 0; i < ncores; ++i)
{
threadHandles.push_back(::CreateThread(NULL, 0, CalcThread, &ranges[i], 0, NULL));
}
int32_t remainderResult = Sum(remainderRange.data, remainderRange.length);
DWORD waitResult = ::WaitForMultipleObjects((DWORD)threadHandles.size(), &threadHandles[0], TRUE, INFINITE);
if (WAIT_OBJECT_0 == waitResult)
{
for (auto& r : ranges)
{
result += r.result;
}
result += remainderResult;
}
else
{
throw std::runtime_error("Something went horribly - HORRIBLY wrong!");
}
for (auto& h : threadHandles)
{
::CloseHandle(h);
}
return result;
}
static int32_t TestWithSTLThreads(const Range& range, size_t ncores)
{
int32_t result = 0;
std::vector<Range> ranges;
size_t nextStart = 0;
size_t chunkLength = range.length / ncores;
size_t remainder = range.length - chunkLength * ncores;
while (nextStart < range.length)
{
ranges.push_back(Range(&range.data[nextStart], chunkLength));
nextStart += chunkLength;
}
Range remainderRange(&range.data[range.length - remainder], remainder);
std::vector<std::thread> threads;
for (size_t i = 0; i < ncores; ++i)
{
threads.push_back(std::thread([](Range* range){ range->result = Sum(range->data, range->length); }, &ranges[i]));
}
int32_t remainderResult = Sum(remainderRange.data, remainderRange.length);
for (auto& t : threads)
{
t.join();
}
for (auto& r : ranges)
{
result += r.result;
}
result += remainderResult;
return result;
}
void TestNativeThreads()
{
const size_t DATA_SIZE = 800000000ULL;
typedef std::vector<int32_t> DataVector;
DataVector data;
data.reserve(DATA_SIZE);
for (size_t i = 0; i < DATA_SIZE; ++i)
{
data.push_back(static_cast<int32_t>(i));
}
Range r = { data.data(), data.size() };
std::chrono::system_clock::time_point singleThreadedStart = std::chrono::high_resolution_clock::now();
int32_t result = TestSingleThreaded(r);
std::chrono::system_clock::time_point singleThreadedEnd = std::chrono::high_resolution_clock::now();
std::cout
<< "Single threaded sum: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(singleThreadedEnd - singleThreadedStart).count()
<< "ms." << " Result = " << result << std::endl;
std::chrono::system_clock::time_point multiThreadedStart = std::chrono::high_resolution_clock::now();
result = TestWithNCores(r, 8);
std::chrono::system_clock::time_point multiThreadedEnd = std::chrono::high_resolution_clock::now();
std::cout
<< "Multi threaded sum: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(multiThreadedEnd - multiThreadedStart).count()
<< "ms." << " Result = " << result << std::endl;
std::chrono::system_clock::time_point stdThreadedStart = std::chrono::high_resolution_clock::now();
result = TestWithSTLThreads(r, 8);
std::chrono::system_clock::time_point stdThreadedEnd = std::chrono::high_resolution_clock::now();
std::cout
<< "std::thread sum: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(stdThreadedEnd - stdThreadedStart).count()
<< "ms." << " Result = " << result << std::endl;
}
Single threaded sum: 382ms. Result = -532120576
Multi threaded sum: 234ms. Result = -532120576
std::thread sum: 245ms. Result = -532120576
Press any key to continue . . ..
start = ...
for (auto& val : ints)
sum += val;
end = ...
diff = end - start;
sum = 0;
start = ...
// do nothing...
end = ...
diff = end - start;