【问题标题】:Why 8 threads is slower than 2 threads?为什么 8 个线程比 2 个线程慢?
【发布时间】:2016-10-29 21:43:51
【问题描述】:

我必须先为我糟糕的英语道歉。 我现在正在学习硬件事务内存,我正在使用 TBB 中的 spin_rw_mutex.h 在 C++ 中实现事务块。 speculative_spin_rw_mutex是spin_rw_mutex中的一个类。h是一个互斥体,已经实现了intel TSX的RTM接口。

我用来测试RTM的例子很简单。我创建了 Account 类,并随机将资金从一个帐户转移到另一个帐户。所有帐户都在一个帐户数组中,大小为100。随机函数在boost中。(我认为STL具有相同的随机函数)。传递函数受 speculative_spin_rw_mutex 保护。我使用 tbb::parallel_for 和 tbb::task_scheduler_init 来控制并发。所有传输方法都在 paraller_for 的 lambda 中调用。总传输次数为 100 万次。奇怪的是,当 task_scheduler_init 设置为 2 时,程序是最快的(8 秒)。事实上,我的 CPU 是 i7 6700k,它有 8 个线程。在 8 到 50,000 的范围内,程序的性能几乎没有变化(11 到 12 秒)。当我将 task_scheduler_init 增加到 100,000 时,运行时间将增加到大约 18 秒。 我尝试使用分析器分析程序,发现热点函数是互斥锁。但是我认为事务回滚率并没有那么高。不知道为什么程序这么慢。

有人说虚假分享会降低性能,结果我尝试使用

std::vector> cache_aligned_accounts(AccountsSIZE,Account(1000));

替换原来的数组

账户* 账户[AccountsSIZE];

避免虚假分享。似乎没有任何改变; 这是我的新代码。

#include <tbb/spin_rw_mutex.h> #include <iostream> #include "tbb/task_scheduler_init.h" #include "tbb/task.h" #include "boost/random.hpp" #include <ctime> #include <tbb/parallel_for.h> #include <tbb/spin_mutex.h> #include <tbb/cache_aligned_allocator.h> #include <vector> using namespace tbb; tbb::speculative_spin_rw_mutex mu; class Account { private: int balance; public: Account(int ba) { balance = ba; } int getBalance() { return balance; } void setBalance(int ba) { balance = ba; } }; //Transfer function. Using speculative_spin_mutex to set critical section void transfer(Account &from, Account &to, int amount) { speculative_spin_rw_mutex::scoped_lock lock(mu); if ((from.getBalance())<amount) { throw std::invalid_argument("Illegal amount!"); } else { from.setBalance((from.getBalance()) - amount); to.setBalance((to.getBalance()) + amount); } } const int AccountsSIZE = 100; //Random number generater and distributer boost::random::mt19937 gener(time(0)); boost::random::uniform_int_distribution<> distIndex(0, AccountsSIZE - 1); boost::random::uniform_int_distribution<> distAmount(1, 1000); /* Function of transfer money */ void all_transfer_task() { task_scheduler_init init(10000);//Set the number of tasks can be run together /* Initial accounts, using cache_aligned_allocator to avoid false sharing */ std::vector<Account, cache_aligned_allocator<Account>> cache_aligned_accounts(AccountsSIZE,Account(1000)); const int TransferTIMES = 10000000; //All transfer tasks parallel_for(0, TransferTIMES, 1, [&](int i) { try { transfer(cache_aligned_accounts[distIndex(gener)], cache_aligned_accounts[distIndex(gener)], distAmount(gener)); } catch (const std::exception& e) { //cerr << e.what() << endl; } //std::cout << distIndex(gener) << std::endl; }); std::cout << cache_aligned_accounts[0].getBalance() << std::endl; int total_balance = 0; for (size_t i = 0; i < AccountsSIZE; i++) { total_balance += (cache_aligned_accounts[i].getBalance()); } std::cout << total_balance << std::endl; }

【问题讨论】:

  • 可能是false sharing
  • 这可能取决于您的操作系统架构实际可用的 CPU 内核数量。
  • 我的 CPU 是 i7 6700k。 2个线程甚至比物理核心数还要少。
  • 如前所述,“虚假共享”可能会产生影响。此外,如果您并行执行的工作被同步开销所掩盖,那么这可能会伤害您。与 2 个线程相比,具有 8 个线程的操作系统也有额外的调度开销。
  • 记住操作系统必须调度线程。他们不单独运作。所以每个线程都有开销。

标签: c++ multithreading tbb intel-tsx


【解决方案1】:

虽然我无法重现您的基准,但我在这里看到了导致此行为的两个可能原因:

  • “煮汤的厨师太多”:您使用单个 spin_rw_mutex 被所有线程的所有传输锁定。在我看来,您的转移是按顺序执行的。这可以解释为什么配置文件在那里看到热点。英特尔页面警告在这种情况下性能下降。

  • 吞吐量与速度:在 i7 上,in a couple of benchmarks, I could notice 表示,当您使用更多内核时,每个内核的运行速度都会慢一些,因此固定 siez 循环的总时间会更长。然而,计算总吞吐量(即在所有这些并行循环中发生的事务总数),吞吐量要高得多(尽管与内核数量不完全成正比)。

我宁愿选择第一种情况,但第二种情况不排除。

【讨论】:

  • 我认为你是对的,我正在尝试在 Account 类中使用互斥锁
【解决方案2】:

由于英特尔 TSX 在高速缓存行粒度上工作,因此绝对要从错误共享开始。不幸的是,cache_aligned_allocator 并不符合您的预期,即它对齐了整个 std::vector,但您需要单独的 Account 占用整个缓存行以防止错误共享。

【讨论】:

    猜你喜欢
    • 2014-07-21
    • 2021-05-08
    • 1970-01-01
    • 1970-01-01
    • 2015-07-09
    • 1970-01-01
    • 2011-08-14
    • 2021-03-27
    • 1970-01-01
    相关资源
    最近更新 更多