tbb:concurrent_hash_map< K,V&gt ;:英特尔线程构建模块(TBB)的示例代码 [英] tbb:concurrent_hash_map<K,V>: sample code for Intel Threading Building Blocks (TBB)

查看:227
本文介绍了tbb:concurrent_hash_map< K,V&gt ;:英特尔线程构建模块(TBB)的示例代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

正在寻找示例代码以使用Intel Threading Building Blocks(TBB)中的tbb::concurrent_hash_map<K,V>.

Looking for sample code to use tbb::concurrent_hash_map<K,V> from Intel Threading Building Blocks (TBB).

我可以插入,但似乎无法读回值.

I can insert, but I cannot seem to read the values back.

示例代码似乎缺少英特尔官方文档一侧.

The official Intel documentation appears to be somewhat lacking on the sample code side.

最好的文档在Voss的"Pro TBB:带有线程构建块的C ++并行编程"中. 免费下载这本书(它是公共领域).

The best docs are in "Pro TBB: C++ Parallel Programming with Threading Building Blocks" by Voss. Download this book for free (it's public domain).

忽略英特尔文档.它们本质上是功能签名的集合.

Ignore the Intel docs. They are essentially a collection of function signatures.

推荐答案

英特尔TBB 是开源的,并且在 GitHub 上:

Intel TBB is open source, and on GitHub:

https://github.com/intel/tbb

要安装TBB,我使用了 vcpkg ,它与LinuxWindowsMac.是的,vcpkg来自微软,但是它是100%跨平台的,开源的并且非常流行.

To install TBB, I used vcpkg which is compatible with Linux, Windows and Mac. Yes, vcpkg is from Microsoft, but it is 100% cross-platform, open source, and very popular.

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.

编译:

  • 与任何现代编译器(包括MSVC,GCC,LLVM,英特尔编译器(ICC)等)兼容.我在gcc中使用CMake.
  • Compatible with any modern compiler including MSVC, GCC, LLVM, Intel Compiler (ICC), etc. I used CMake for gcc.

还可以下载源代码并将标头和库提取到源代码树中,这同样有效.

Can also download the source and extract the headers and libraries into the source tree, this works just as well.

代码.

#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 <<.

Printing uses {fmtlib} for printing; can replace with 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

其他哈希图

  • 请参阅: https://tessil.github.io /2016/08/29/benchmark-hopscotch-map.html
  • 请参阅:std::unordered_map.它具有更标准的API,并且在许多情况下是线程安全的,请参阅: unordered_map线程安全 .如果可能的话,建议使用它,因为它具有更简单的API.
  • 还有英特尔TBB的concurrent_unordered_map.本质上是同一件事,一个键/值映射.但是,它年代久远,级别低得多,并且使用起来更困难.必须提供一个哈希器,一个相等运算符和一个分配器.即使在官方的英特尔文档中,也没有任何示例代码.尽管有数月的不时尝试,但我从未使它起作用.它可能已过时,因为在上述免费书中未提及(仅涵盖concurrent_hash_map).不推荐.
  • Other hash maps

    • See: https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html
    • See: std::unordered_map. This has a more standard API, and is thread safe in many situations, see: unordered_map thread safety. Suggest using this, if possible, as it has a simpler API.
    • There is also the concurrent_unordered_map from Intel TBB. It is essentially the same thing, a key/value map. However, it is much older, much much lower level, and more difficult to use. One has to supply a hasher, a equality operator, and an allocator. There is no sample code anywhere, even in the official Intel docs. I never got it working, despite months of occasional attempts. It may be obsolete, as it is not mentioned in said free book (it only covers concurrent_hash_map). Not recommended.
    • 实际上有两个访问器,一个是读锁,一个是写锁:

      There are actually two accessors, one is a read lock, one is a write lock:

      • const_accessor
      • accessor
      • const_accessor
      • accessor

      如果使用find,请使用const_accessor,这是一个读锁.如果使用inserterase,请使用accessor这是一个写锁(即它将等待直到完成所有读取,然后阻止进一步的读取,直到完成).

      If using find, use const_accessor which is a read lock. If using insert or erase, use accessor which is a write lock (i.e. it will wait until any reads are done, and block further reads until it is done).

      这实际上等效于读者/作家锁,而是按字典中的单个字典键,而不是整个字典.

      This is effectively equivalent to a reader/writer lock, but on a single dictionary key in the dictonary, rather than the entire dictionary.

      学习曲线的最后部分:对于键写操作,直到访问器超出范围才发生任何事情.因此,可以使用CAS(比较和交换)来保留不超过几条机器指令的任何锁.

      Final part of the learning curve: for key writes, nothing happens until the accessor goes out of scope. So any locks are held for no more than a few machine instructions, probably using CAS (Compare And Swap).

      将其与数据库进行比较,访问器的范围就像一个事务.当访问器超出范围时,整个事务都将提交给哈希映射.

      Comparing this to a database, the scope of the accessor is like a transaction. When the accessor goes out of scope, the entire transaction is committed to the hashmap.

      上面提到的免费书在concurrent_hash_map的一章中具有出色的性能提示.

      The free book mentioned above has fantastic performance tips in the chapter on concurrent_hash_map.

      此哈希映射的API功能强大,但有些尴尬.但是,它支持在插入/更新时进行细粒度的每键锁定.只能使用 CAS 来锁定少数机器指令.这是其他任何语言都无法用任何语言提供的哈希图.为了简单起见,建议从std::unordered_map开始;只要两个线程不写相同的密钥,它就是线程安全的.如果需要极快的性能,则可以选择重构或使用[]访问器和insert_or_update()在顶部编写兼容的包装.

      The API for this hash map is powerful but somewhat awkward. However, it supports fine-grained, per-key locks on insert/update. Any locks are only held for a handful of machine instructions, using CAS. This is something that few other hashmaps can offer, in any language. Recommend starting with std::unordered_map for simplicity; it is thread safe as long as the two threads do not write to the same key. If blazingly fast performance is required, there is an option to either refactor, or write a compatible wrapper on top with [] accessors and insert_or_update().

      这篇关于tbb:concurrent_hash_map&lt; K,V&gt ;:英特尔线程构建模块(TBB)的示例代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆