std::thread 运行速度比 std::future 慢很多
std::thread runs A LOT slower than std::future
我有一些带有 Mainloop
的简单渲染程序,它在一个线程上以大约 8000 fps 的速度运行(它除了绘制背景外什么都不做),我想看看另一个线程渲染是否会扰乱当前上下文没有改变它(这并不令我惊讶)。我在这里用这个简单的代码实现了这一点,
m_Thread = std::thread(Mainloop);
m_Thread.join();
这里的代码不知何故 运行 非常慢,~30 FPS。我觉得这很奇怪,我记得在另一个项目中出于类似的基于性能的原因我使用了 std::future
。所以我然后用下面的代码用 std::future
试了一下:
m_Future = std::async(std::launch::async, Mainloop);
m_Future.get();
这比单线程性能 (~7900) fps 略低一点。为什么 std::thread
比 std::future
慢这么多?
编辑:
忽略上面的代码,这是一个最小的可重现示例,只需将 THREAD
切换为 0
或 1
即可进行比较:
#include <future>
#include <chrono>
#include <Windows.h>
#include <iostream>
#include <string>
#define THREAD 1
static void Function()
{
}
int main()
{
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point finish = std::chrono::high_resolution_clock::now();
long double difference = 0;
long long unsigned int fps = 0;
#if THREAD
std::thread worker;
#else
std::future<void> worker;
#endif
while (true)
{
//FPS
finish = std::chrono::high_resolution_clock::now();
difference = std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start).count();
difference = difference / 1000000000;
if (difference > 0.1) {
start = std::chrono::high_resolution_clock::now();
std::wstring fpsStr = L"Fps: ";
fpsStr += std::to_wstring(fps);
SetConsoleTitle(fpsStr.c_str());
fps = 0;
}
#if THREAD
worker = std::thread(Function);
worker.join();
#else
worker = std::async(std::launch::async, Function);
worker.get();
#endif
fps += 10;
}
return 0;
}
std::async
可以用不同的方式实现。例如,可以有一个预先分配的线程池,每次在循环中使用 std::async
时,您只需重用池中的“热”线程。
std::thread
每次使用都会创建一个新的系统线程对象。与重用池中的线程相比,这可能是一个很大的开销。
我建议您在 std::async
可能开始竞争预分配系统对象的多线程环境中测试您的代码。
在某些版本的 MSVC C++ 标准库中,std::async
从(系统)线程池中提取,而 std::thread
不提取。这可能会导致问题,因为我过去已经用尽它并陷入僵局。也意味着随便用更快。
我的建议是在 std::thread
之上编写您自己的线程池并使用它。您将完全控制有多少线程处于活动状态。
这是一个很难解决的问题,但依靠别人解决它是行不通的,因为老实说,我使用的标准库实现并不能可靠地解决它。
请注意,在N 大小的线程池中,大小为N 的阻塞依赖链将发生死锁。如果你把线程数设为CPU的个数,并且不可靠地重用调用线程,你会发现在4核以上的机器上测试的多线程代码经常在2核机器上死锁。
同时,如果你为每个任务创建一个线程池,并且它们堆叠在一起,你最终会颠簸 CPU。
请注意,该标准对您实际可以期望的线程数含糊不清 运行。虽然 std async 必须表现得“好像”你创建了一个新的 std 线程,但实际上这只意味着它们必须重新初始化并销毁任何 thread_local
对象。
标准中有最终进度保证,但我发现在使用 std::async
时在实际实现中违反了它们。所以我现在避免直接使用它。
我有一些带有 Mainloop
的简单渲染程序,它在一个线程上以大约 8000 fps 的速度运行(它除了绘制背景外什么都不做),我想看看另一个线程渲染是否会扰乱当前上下文没有改变它(这并不令我惊讶)。我在这里用这个简单的代码实现了这一点,
m_Thread = std::thread(Mainloop);
m_Thread.join();
这里的代码不知何故 运行 非常慢,~30 FPS。我觉得这很奇怪,我记得在另一个项目中出于类似的基于性能的原因我使用了 std::future
。所以我然后用下面的代码用 std::future
试了一下:
m_Future = std::async(std::launch::async, Mainloop);
m_Future.get();
这比单线程性能 (~7900) fps 略低一点。为什么 std::thread
比 std::future
慢这么多?
编辑:
忽略上面的代码,这是一个最小的可重现示例,只需将 THREAD
切换为 0
或 1
即可进行比较:
#include <future>
#include <chrono>
#include <Windows.h>
#include <iostream>
#include <string>
#define THREAD 1
static void Function()
{
}
int main()
{
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point finish = std::chrono::high_resolution_clock::now();
long double difference = 0;
long long unsigned int fps = 0;
#if THREAD
std::thread worker;
#else
std::future<void> worker;
#endif
while (true)
{
//FPS
finish = std::chrono::high_resolution_clock::now();
difference = std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start).count();
difference = difference / 1000000000;
if (difference > 0.1) {
start = std::chrono::high_resolution_clock::now();
std::wstring fpsStr = L"Fps: ";
fpsStr += std::to_wstring(fps);
SetConsoleTitle(fpsStr.c_str());
fps = 0;
}
#if THREAD
worker = std::thread(Function);
worker.join();
#else
worker = std::async(std::launch::async, Function);
worker.get();
#endif
fps += 10;
}
return 0;
}
std::async
可以用不同的方式实现。例如,可以有一个预先分配的线程池,每次在循环中使用 std::async
时,您只需重用池中的“热”线程。
std::thread
每次使用都会创建一个新的系统线程对象。与重用池中的线程相比,这可能是一个很大的开销。
我建议您在 std::async
可能开始竞争预分配系统对象的多线程环境中测试您的代码。
在某些版本的 MSVC C++ 标准库中,std::async
从(系统)线程池中提取,而 std::thread
不提取。这可能会导致问题,因为我过去已经用尽它并陷入僵局。也意味着随便用更快。
我的建议是在 std::thread
之上编写您自己的线程池并使用它。您将完全控制有多少线程处于活动状态。
这是一个很难解决的问题,但依靠别人解决它是行不通的,因为老实说,我使用的标准库实现并不能可靠地解决它。
请注意,在N 大小的线程池中,大小为N 的阻塞依赖链将发生死锁。如果你把线程数设为CPU的个数,并且不可靠地重用调用线程,你会发现在4核以上的机器上测试的多线程代码经常在2核机器上死锁。
同时,如果你为每个任务创建一个线程池,并且它们堆叠在一起,你最终会颠簸 CPU。
请注意,该标准对您实际可以期望的线程数含糊不清 运行。虽然 std async 必须表现得“好像”你创建了一个新的 std 线程,但实际上这只意味着它们必须重新初始化并销毁任何 thread_local
对象。
标准中有最终进度保证,但我发现在使用 std::async
时在实际实现中违反了它们。所以我现在避免直接使用它。