Т: concurrent_hash_map : пример кода для Intel Threading Building Blocks (TBB) - PullRequest
1 голос
/ 08 марта 2020

Ищем пример кода для использования tbb::concurrent_hash_map<K,V> от Intel Threading Building Blocks (TBB).

Я могу вставить, но не могу прочитать значения обратно.

* официальная документация Intel , по-видимому, несколько не хватает со стороны примера кода.

Обновление

Лучшие документы находятся в "Pro TBB: параллельное программирование на C ++ со строительными блоками потоков" от Voss , Скачать эту книгу бесплатно (это публичный c домен).

Игнорировать документы Intel. По сути, они представляют собой набор сигнатур функций.

1 Ответ

2 голосов
/ 08 марта 2020

Intel TBB с открытым исходным кодом и на GitHub :

https://github.com/intel/tbb

Для установки TBB я использовал vcpkg , который совместим с Linux, Windows и Mac. Да, vcpkg от Microsoft, но он на 100% кроссплатформенный, с открытым исходным кодом и очень популярен.

Linux:

./vcpkg search tbb              # Find the package.
./vcpkg install tbb:x64-linux   # Install the package.

Windows:

vcpkg search tbb                # Find the package.
vcpkg install tbb:x64-windows   # Install the package.

Компиляция:

  • Совместима с любым современным компилятором, включая MSV C, G CC, LLVM, Intel Compiler (I CC) и др. c. Я использовал CMake для gcc.

Можно также загрузить исходный код и извлечь заголовки и библиотеки в дерево исходных кодов, это также работает.

Код.

#include "tbb/concurrent_hash_map.h" // For concurrent hash map.

tbb::concurrent_hash_map<int, string> dict;
typedef tbb::concurrent_hash_map<int, string>::accessor dictAccessor; // See notes on accessor below.   

print("  - Insert key, method 1:\n");   
dict.insert({1,"k1"});
print("    - 1: k1\n");

print("  - Insert key, method 2:\n");
dict.emplace(2,"k2");
print("    - 2: k2\n");

string result;

{
    print("  - Read an existing key:\n");   
    dictAccessor accessor;
    const auto isFound = dict.find(accessor, 2);
    // The accessor functions as:
    // (a) a fine-grained per-key lock (released when it goes out of scope).
    // (b) a method to read the value.
    // (c) a method to insert or update the value.
    if (isFound == true) {
        print("    - {}: {}\n", accessor->first, accessor->second);
    }
}

{
    print("  - Atomically insert or update a key:\n");  
    dictAccessor accessor;
    const auto itemIsNew = dict.insert(accessor, 4);
    // The accessor functions as:
    // (a) a fine-grained per-key lock (released when it goes out of scope).
    // (b) a method to read the value.
    // (c) a method to insert or update the value.
    if (itemIsNew == true) {
        print("    - Insert.\n");
        accessor->second = "k4";
    }
    else {
        print("    - Update.\n");
        accessor->second = accessor->second + "+update";
    }
    print("    - {}: {}\n", accessor->first, accessor->second);     
}

{
    print("  - Atomically insert or update a key:\n");          
    dictAccessor accessor;
    const auto itemIsNew = dict.insert(accessor, 4);
    // The accessor functions as:
    // (a) a fine-grained per-key lock which is released when it goes out of scope.
    // (b) a method to read the value.
    // (c) a method to insert or update the value.
    if (itemIsNew == true) {
        print("    - Insert.\n");
        accessor->second = "k4";
    }
    else {
        print("    - Update.\n");
        accessor->second = accessor->second + "+update";
    }
    print("    - {}: {}\n", accessor->first, accessor->second);     
}

{
    print("  - Read the final state of the key:\n");            
    dictAccessor accessor;
    const auto isFound = dict.find(accessor, 4);
    print("    - {}: {}\n", accessor->first, accessor->second);
}

Для печати используется {fmtlib} для печати; можно заменить на cout <<.

Вывод:

- Insert key, method 1:
  - 1: k1
- Insert key, method 2:
  - 2: k2
- Read an existing key:
  - 2: k2
- Atomically insert or update a key:
  - Insert.
  - 4: k4
- Atomically insert or update a key:
  - Update.
  - 4: k4+update
- Read the final state of the key:
  - 4: k4+update

Прочие га sh карты

  • См .: https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html
  • См .: std::unordered_map. Он имеет более стандартный API и во многих ситуациях безопасен для потоков, см. unordered_map потокобезопасность . Если возможно, предложите использовать это, так как он имеет более простой API.
  • Существует также concurrent_unordered_map от Intel TBB. По сути, это то же самое, карта ключ / значение. Тем не менее, он намного старше, намного более низкого уровня и более сложен в использовании. Нужно предоставить хеш, оператор равенства и распределитель. Там нет ни одного примера кода, даже в официальных документах Intel. Я никогда не работал, несмотря на месяцы случайных попыток. Это может быть устаревшим, поскольку это не упомянуто в упомянутой бесплатной книге (это только охватывает concurrent_hash_map). Не рекомендуется.

Обновление: блокировки чтения / записи

На самом деле есть два метода доступа, один - блокировка чтения, другой - блокировка записи:

  • const_accessor
  • accessor

При использовании find используйте const_accessor, который является блокировкой чтения. Если вы используете insert или erase, используйте accessor, который является блокировкой записи (т.е. он будет ждать до тех пор, пока не будет выполнено любое чтение, и заблокирует дальнейшее чтение до тех пор, пока это не будет сделано).

Это фактически эквивалентно к блокировке чтения / записи , но по одному словарному ключу в словаре, а не по всему словарю.

Обновление

Заключительная часть кривой обучения: для ключ пишет, ничего не происходит, пока средство доступа не выходит из области видимости. Поэтому любые блокировки удерживаются не более чем для нескольких машинных инструкций, возможно, с использованием CAS (Compare And Swap).

Сравнивая это с базой данных, область действия средства доступа похожа на транзакцию. Когда средство доступа выходит из области видимости, вся транзакция передается в хэш-карту.

Обновление

В упомянутой выше бесплатной книге есть советы по производительности c в главе concurrent_hash_map.

Заключение

API для этой карты ha sh мощный, но несколько неловкий. Тем не менее, он поддерживает детальную блокировку для каждого ключа при вставке / обновлении. Любые блокировки сохраняются только для нескольких машинных инструкций, используя CAS . Это то, что немногие другие хешмапы могут предложить на любом языке. Для простоты рекомендуем начинать с std::unordered_map; потокобезопасен, если два потока не записывают в один и тот же ключ . Если требуется невероятно высокая производительность, есть возможность либо изменить рефакторинг, либо написать совместимую оболочку поверх с [] аксессорами и insert_or_update().

...