Сначала я должен извиниться за мой плохой английский.
Сейчас я изучаю аппаратную транзакционную память и использую spin_rw_mutex.h в TBB для реализации блока транзакций в C ++. speculative_spin_rw_mutex — это класс в spin_rw_mutex.h — мьютекс, в котором уже реализован RTM-интерфейс Intel TSX.
Пример, который я использовал для тестирования RTM, очень прост. Я создал класс Account и переводил деньги с одного счета на другой в случайном порядке. Все учетные записи находятся в массиве учетных записей, и их размер равен 100. Случайная функция активна (я думаю, что STL имеет такую же случайную функцию). Передаточная функция защищена с помощью speculative_spin_rw_mutex. Я использовал tbb :: parallel_for и tbb :: task_scheduler_init для управления параллелизмом. Все методы передачи вызываются в лямбда-выражении paraller_for. Общее время передачи составляет 1 миллион. Странно то, что когда для task_scheduler_init установлено значение 2, программа работает быстрее всего (8 секунд). Фактически мой процессор — i7 6700k, который имеет 8 потоков. В диапазоне от 8 до 50000 производительность программы практически не меняется (от 11 до 12 секунд). Когда я увеличу значение task_scheduler_init до 100 000, время выполнения увеличится примерно до 18 секунд.
Я попытался использовать профилировщик для анализа программы и обнаружил, что функция горячей точки — это мьютекс. Однако я думаю, что скорость отката транзакций не так высока. Я не знаю, почему программа такая медленная.
Кто-то говорит, что ложный обмен замедляет производительность, в результате я попытался использовать
std :: vector> cache_aligned_accounts (AccountsSIZE, Account (1000));
заменить оригинальный массив
Аккаунт * account [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;
}
Хотя я не могу воспроизвести ваш тест, я вижу здесь две возможные причины такого поведения:
«Слишком много поваров варят суп»: вы используете один spin_rw_mutex это заблокировано всеми передачами всеми потоками. Сдается мне, что ваши переводы выполняются последовательно. Это объясняет, почему профиль видит горячую точку там. Страница Intel предупреждает о снижении производительности в таком случае.
Пропускная способность и скорость: на i7, в нескольких тестах я мог заметить что при использовании большего количества ядер каждое ядро работает немного медленнее, поэтому общее время фиксированных циклов siez увеличивается. Однако при подсчете общей пропускной способности (то есть общего количества транзакций, которые происходят во всех этих параллельных циклах) пропускная способность намного выше (хотя не полностью пропорционально количеству ядер).
Я предпочел бы выбрать первый случай, но второй не исключать.
Поскольку Intel TSX работает над детализацией строк кэша, ложное совместное использование определенно является началом. К сожалению, cache_aligned_allocator делает не то, что вы, вероятно, ожидаете, то есть выровнял весь std :: vector, но вам нужна отдельная учетная запись, чтобы занять всю строку кэша, чтобы предотвратить ложное совместное использование.