2个线程比1慢
2 threads slower than 1?
我正在玩std::thread
,一些奇怪的东西弹出:
#include <thread>
int k = 0;
int main() {
std::thread t1([]() { while (k < 1000000000) { k = k + 1; }});
std::thread t2([]() { while (k < 1000000000) { k = k + 1; }});
t1.join();
t2.join();
return 0;
}
当使用clang++编译上述代码时,没有进行任何优化,我得到了以下基准测试:
real 0m2.377s
user 0m4.688s
sys 0m0.005s
然后我把我的代码改成如下:(现在只使用1个线程)
#include <thread>
int k = 0;
int main() {
std::thread t1([]() { while (k < 1000000000) { k = k + 1; }});
t1.join();
return 0;
}
这些是新的基准:
real 0m2.304s
user 0m2.298s
sys 0m0.003s
为什么使用2个线程的代码比使用1个线程的代码慢?
您有两个线程争夺同一个变量k
。所以你在处理器说"处理器1:嘿,你知道k
的值是多少吗?"处理器2:当然,给你!",每隔几次更新就会来回乒乓声。由于k
不是原子的,因此也不能保证thread2不会写入k
的"旧"值,因此下一次线程1读取该值时,它会跳回1、2、10或100步,并且必须重新执行—理论上,这可能导致两个循环都没有完成,但这需要相当多的坏运气。
这实际上应该是对Mats Petersson的回答的回复,但是我想提供代码示例。
这个问题是一个特定的资源争用,也是一个缓存。
替代1:#include <cstdint>
#include <thread>
#include <vector>
#include <stdlib.h>
static const uint64_t ITERATIONS = 10000000000ULL;
int main(int argc, const char** argv)
{
size_t numThreads = 1;
if (argc > 1) {
numThreads = strtoul(argv[1], NULL, 10);
if (numThreads == 0)
return -1;
}
std::vector<std::thread> threads;
uint64_t k = 0;
for (size_t t = 0; t < numThreads; ++t) {
threads.emplace_back([&k]() { // capture k by reference so we all use the same k.
while (k < ITERATIONS) {
k++;
}
});
}
for (size_t t = 0; t < numThreads; ++t) {
threads[t].join();
}
return 0;
}
这里,线程争用一个变量,同时执行读和写,这迫使它乒乓产生争用,使单线程的情况是最有效的。
#include <cstdint>
#include <thread>
#include <vector>
#include <stdlib.h>
#include <atomic>
static const uint64_t ITERATIONS = 10000000000ULL;
int main(int argc, const char** argv)
{
size_t numThreads = 1;
if (argc > 1) {
numThreads = strtoul(argv[1], NULL, 10);
if (numThreads == 0)
return -1;
}
std::vector<std::thread> threads;
std::atomic<uint64_t> k = 0;
for (size_t t = 0; t < numThreads; ++t) {
threads.emplace_back([&]() {
// Imperfect division of labor, we'll fall short in some cases.
for (size_t i = 0; i < ITERATIONS / numThreads; ++i) {
k++;
}
});
}
for (size_t t = 0; t < numThreads; ++t) {
threads[t].join();
}
return 0;
}
在这里,我们确定地划分了工作(我们遇到了numThreads不是迭代的除数的情况,但对于本演示来说它已经足够接近了)。不幸的是,我们仍然遇到内存中共享元素访问的争用。
#include <cstdint>
#include <thread>
#include <vector>
#include <stdlib.h>
#include <atomic>
static const uint64_t ITERATIONS = 10000000000ULL;
int main(int argc, const char** argv)
{
size_t numThreads = 1;
if (argc > 1) {
numThreads = strtoul(argv[1], NULL, 10);
if (numThreads == 0)
return -1;
}
std::vector<std::thread> threads;
std::vector<uint64_t> ks;
for (size_t t = 0; t < numThreads; ++t) {
threads.emplace_back([=, &ks]() {
auto& k = ks[t];
// Imperfect division of labor, we'll fall short in some cases.
for (size_t i = 0; i < ITERATIONS / numThreads; ++i) {
k++;
}
});
}
uint64_t k = 0;
for (size_t t = 0; t < numThreads; ++t) {
threads[t].join();
k += ks[t];
}
return 0;
}
同样,这是关于工作负载分布的确定性,并且我们在最后花费少量的精力来整理结果。但是,我们没有采取任何措施来确保计数器的分布有利于健康的CPU分布。:
#include <cstdint>
#include <thread>
#include <vector>
#include <stdlib.h>
static const uint64_t ITERATIONS = 10000000000ULL;
#define CACHE_LINE_SIZE 128
int main(int argc, const char** argv)
{
size_t numThreads = 1;
if (argc > 1) {
numThreads = strtoul(argv[1], NULL, 10);
if (numThreads == 0)
return -1;
}
std::vector<std::thread> threads;
std::mutex kMutex;
uint64_t k = 0;
for (size_t t = 0; t < numThreads; ++t) {
threads.emplace_back([=, &k]() {
alignas(CACHE_LINE_SIZE) uint64_t myK = 0;
// Imperfect division of labor, we'll fall short in some cases.
for (uint64_t i = 0; i < ITERATIONS / numThreads; ++i) {
myK++;
}
kMutex.lock();
k += myK;
kMutex.unlock();
});
}
for (size_t t = 0; t < numThreads; ++t) {
threads[t].join();
}
return 0;
}
在这里,我们避免了线程之间的争用,直到缓存行级别,除了最后使用互斥锁来控制同步的情况。对于这种微不足道的工作负载,互斥锁将会有一个巨大的相对成本。或者,您可以使用alignas在外部作用域为每个线程提供自己的存储空间,并在连接之后总结结果,从而消除对互斥锁的需求。
在我看来,比"为什么这不起作用"更重要的问题是"我如何让它工作?"对于手头的任务,我认为std::async
(尽管有明显的缺点)确实是比直接使用std::thread
更好的工具。
#include <future>
#include <iostream>
int k = 0;
unsigned tasks = std::thread::hardware_concurrency();
unsigned reps = 1000000000 / tasks;
int main() {
std::vector<std::future<int>> f;
for (int i=0; i<tasks; i++)
f.emplace_back(std::async(std::launch::async,
[](){int j; for (j=0; j<reps; j++); return j;})
);
for (int i=0; i<tasks; i++) {
f[i].wait();
k += f[i].get();
}
std::cout << k << "n";
return 0;
}
我遇到了这个问题。我的观点是,对于某些类型的作业,管理线程的成本可能大于在线程中运行所带来的好处。下面是我的代码示例,在一个循环中做一些实际的工作,大量的迭代,所以我得到了非常一致的数字与时间命令。
pair<int,int> result{0,0};
#ifdef USETHREAD
thread thread_l(&Myclass::trimLeft, this, std::ref(fsq), std::ref(oriencnt), std::ref(result.first));
thread thread_r(&Myclass::trimRight, this, std::ref(fsq), std::ref(oriencnt), std::ref(result.second));
thread_l.join();
thread_r.join();
#else
// non threaded version faster
trimLeft(fsq, oriencnt, result.first);
trimRight(fsq, oriencnt, result.second);
#endif
return result;
时间结果
Thead No_thread
===========================
Real 4m28s 2m49s
usr 0m55s 2m49s
sys 0m6.2s 0m0.012s
对于大的数,我忽略了秒的小数。我的代码只更新一个面向的共享变量。我还没有让它更新fsq。看起来在线程版本中,系统做了更多的工作,这导致了更长的时钟时间(实时)。我的编译器标志是默认的-g -O2,不确定这是否是关键问题。当使用-O3编译时,差异很小。还有一些互斥控制的IO操作。我的实验表明,这并不是造成差异的原因。我使用gcc 5.4与c++ 11。一种可能是库没有优化。
这里用O3
编译 Thead No_thread
=========================
real 4m24 2m44s
usr 0m54s 2m44s
sys 0m6.2s 0m0.016s
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 用于矢量处理的多个线程
- 如何在多个线程中创建 QSql数据库连接时防止名称冲突
- 如何声明由多个线程调用的 C++ DLL 的内部类,而无需导出类
- 在两个线程上读/写 64 位,无互斥/锁定/原子
- 自 Windows 10 20H1 以来,具有单独线程的多个窗口停止工作
- ASIO signal_set多个 IO 线程不可靠,具体取决于代码顺序?
- 将 10 个线程与原子布尔值同步
- 通过插槽和信号在不同线程中的两个qt对象之间进行通信
- C++线程安全:如果只有一个线程可以写入非原子变量,但多个线程从中读取. 会遇到问题吗?
- C++:在多个线程中访问同一数组/向量的不同单元格是否会产生数据竞赛?
- 一个线程等待多个线程事件
- 如果在 2 个线程中使用,是否值得将size_t声明为 std::atomic?
- 餐饮哲学家问题 - 只有 2 个线程工作
- 如果两个线程相互依赖,则 cpp 线程连接应使用连接导致死锁
- std::cout 来自多个线程
- Qt 线程两个参数
- 我们是否需要每个线程多个io_service用于具有单个接受器的线程 boost::asio 服务器
- C++11线程:多个线程正在等待一个条件变量
- 多线程两个功能使用openMP