2

我必须先为我糟糕的英语道歉。我现在正在学习硬件事务内存,我正在使用 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; }

4

2 回答 2

2

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

于 2016-06-28T10:49:55.257 回答
1

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

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

  • 吞吐量与速度:在 i7 上,在几个基准测试中,我注意到当您使用更多内核时,每个内核的运行速度都会慢一些,因此固定 siez 循环的总时间会更长。然而,计算总吞吐量(即在所有这些并行循环中发生的事务总数),吞吐量要高得多(尽管与内核数量不完全成正比)。

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

于 2016-06-27T19:48:14.127 回答