tbb:concurrent_hash_map<K,V>:英特尔线程构建模块 (TBB) 的示例代码
tbb:concurrent_hash_map<K,V>: sample code for Intel Threading Building Blocks (TBB)
正在寻找示例代码以使用来自英特尔线程构建模块 (TBB) 的 tbb::concurrent_hash_map<K,V>
。
我可以插入,但我似乎无法读回值。
official Intel documentation 在示例代码方面似乎有些欠缺。
更新
最好的文档在 Voss 的 "Pro TBB: C++ Parallel Programming with Threading Building Blocks" 中。 Download this book for free(它是 public 域)。
忽略英特尔文档。它们本质上是函数签名的集合。
Intel TBB is open source, and on GitHub:
https://github.com/intel/tbb
为了安装 TBB,我使用了与 Linux
、Windows
和 Mac
兼容的 vcpkg。是的,vcpkg 来自微软,但是它是 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.
编译:
- 与任何现代编译器兼容,包括 MSVC、GCC、LLVM、英特尔编译器 (ICC) 等。我将
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
其他哈希映射
- 参见:https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html
- 参见:
std::unordered_map
。这具有更标准的 API,并且在许多情况下是线程安全的,请参阅:unordered_map thread safety。建议尽可能使用它,因为它具有更简单的 API.
- 还有来自 Intel TBB 的
concurrent_unordered_map
。它本质上是同一件事,一个 key/value 地图。但是,它要老得多,级别低得多,而且更难使用。必须提供散列器、相等运算符和分配器。任何地方都没有示例代码,即使在官方英特尔文档中也是如此。尽管偶尔尝试了几个月,但我从来没有让它工作过。它可能已过时,因为该免费书籍中未提及(它仅涵盖 concurrent_hash_map
)。不推荐。
更新:Reader/Writer 锁
访问器其实有两种,一种是读锁,一种是写锁:
const_accessor
accessor
如果使用 find
,请使用 const_accessor
,这是一个读锁。如果使用 insert
或 erase
,请使用 accessor
这是一个写锁(即它将等待直到完成任何读取,并阻止进一步的读取直到完成)。
这实际上等同于 reader/writer lock,但在字典中的单个字典键上,而不是整个字典。
更新
学习曲线的最后一部分:对于键写入,在访问器超出范围之前不会发生任何事情。因此,任何锁都不会超过几条机器指令,可能使用 CAS(比较和交换)。
将其与数据库进行比较,访问器的范围就像一个事务。当访问器超出范围时,整个事务将提交到哈希映射。
更新
上面提到的免费书籍在 concurrent_hash_map
的章节中有很棒的性能提示。
结论
这个哈希映射的 API 很强大,但有点笨拙。但是,它支持 insert/update 上的细粒度、按键锁定。使用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,任何锁都只会为少数机器指令持有。如果需要极快的性能,可以选择重构,或者使用 []
访问器和 insert_or_update()
.
在顶部编写兼容的包装器
正在寻找示例代码以使用来自英特尔线程构建模块 (TBB) 的 tbb::concurrent_hash_map<K,V>
。
我可以插入,但我似乎无法读回值。
official Intel documentation 在示例代码方面似乎有些欠缺。
更新
最好的文档在 Voss 的 "Pro TBB: C++ Parallel Programming with Threading Building Blocks" 中。 Download this book for free(它是 public 域)。
忽略英特尔文档。它们本质上是函数签名的集合。
Intel TBB is open source, and on GitHub:
https://github.com/intel/tbb
为了安装 TBB,我使用了与 Linux
、Windows
和 Mac
兼容的 vcpkg。是的,vcpkg 来自微软,但是它是 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.
编译:
- 与任何现代编译器兼容,包括 MSVC、GCC、LLVM、英特尔编译器 (ICC) 等。我将
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
其他哈希映射
- 参见:https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html
- 参见:
std::unordered_map
。这具有更标准的 API,并且在许多情况下是线程安全的,请参阅:unordered_map thread safety。建议尽可能使用它,因为它具有更简单的 API. - 还有来自 Intel TBB 的
concurrent_unordered_map
。它本质上是同一件事,一个 key/value 地图。但是,它要老得多,级别低得多,而且更难使用。必须提供散列器、相等运算符和分配器。任何地方都没有示例代码,即使在官方英特尔文档中也是如此。尽管偶尔尝试了几个月,但我从来没有让它工作过。它可能已过时,因为该免费书籍中未提及(它仅涵盖concurrent_hash_map
)。不推荐。
更新:Reader/Writer 锁
访问器其实有两种,一种是读锁,一种是写锁:
const_accessor
accessor
如果使用 find
,请使用 const_accessor
,这是一个读锁。如果使用 insert
或 erase
,请使用 accessor
这是一个写锁(即它将等待直到完成任何读取,并阻止进一步的读取直到完成)。
这实际上等同于 reader/writer lock,但在字典中的单个字典键上,而不是整个字典。
更新
学习曲线的最后一部分:对于键写入,在访问器超出范围之前不会发生任何事情。因此,任何锁都不会超过几条机器指令,可能使用 CAS(比较和交换)。
将其与数据库进行比较,访问器的范围就像一个事务。当访问器超出范围时,整个事务将提交到哈希映射。
更新
上面提到的免费书籍在 concurrent_hash_map
的章节中有很棒的性能提示。
结论
这个哈希映射的 API 很强大,但有点笨拙。但是,它支持 insert/update 上的细粒度、按键锁定。使用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,任何锁都只会为少数机器指令持有。如果需要极快的性能,可以选择重构,或者使用 []
访问器和 insert_or_update()
.