Open ktprime opened 2 years ago
https://gitee.com/shines77/emhash 已经审核通过了。
https://github.com/shines77/jstd_hashmap 也修改了一个错误,开启了 hash_policy 。
可以使用下面命令更新:
git pull
git submodule update --init --recursive
如果你已经手动添加过 ./3rd_party/emhash,可以参考这个文章删除子模块:
然后使用下面命令添加 submodule :
git submodule add --force -b master -- "https://gitee.com/shines77/emhash.git" "./3rd_party/emhash"
该命令最好在前面两步完成了再使用,之后应该就能正常使用 git submodule update --init --recursive
更新 ./3rd_party/emhash
了。
我的hashmap 里面有一个性能分析函数dump_statics()(宏开启),统计查找命中,失败,冲突率,cache miss 之类非常全面性能数据
我也写过类似的:
https://github.com/shines77/jstd_hashmap/blob/master/src/jstd/hashmap/hashmap_analyzer.h
不过,对于非链表的哈希表,要改一下。
/mnt/f/emhash/bench/hashmap-benchmark/3rd_party/jstd_hashmap/src/jstd/hashmap/robin_hash_map.h:2596:53: error: no matching function for call to ‘jstd::robin_hash_map<HashObject<long unsigned int, 256, 32>, long unsigned int, HashFn<long unsigned int, 256, 32, false>, std::equal_to<HashObject<long unsigned int, 256, 32>>, std::allocator<std::pair<HashObject<long unsigned int, 256, 32>* const, long unsigned int> > >::map_group::matchHashAndDistance(uint8_t&, uint8_t&) const’ 2596 | auto mask32 = group.matchHashAndDistance(ctrl_hash, distance);
/jstd/hashmap/robin_hash_map.h:2596 这行的错误,改成:
group.matchHashAndDistance(dist_and_hash);
不过,这个文件的版本有点旧了吧,最新的代码不是这样的。那些 warning 可以用编译开关关掉,只是拿来调试用的。
另外,最好不要在你的 emhash
里以 submodule
的方式引入 /hashmap-benchmark
,因为可能会造成循环引用。可以 submodule
我的 /jstd_hashmap
,因为后者没有引入其他库。此外,/jstd_hashmap
的代码其实是 "Header only" 的,CMakeLists.txt 做了一个 jstd 的静态库只是为以后准备的,不是必须的。因为使用了 SIMD
指令,编译选项里保证有 -march=native
,让编译器能检测是否支持 SIMD
就可以了,最低要求 C++11。
template<class hash_type>
void find_hit_100(const hash_type& ht_hash, const std::string& hash_name, const std::vector<keyType>& vList)
{
auto ts1 = getus(); size_t sum = 0;
for (const auto& v : vList) {
sum += ht_hash.count(v);
#if FL1
if (sum % (1024 * 64) == 0)
memset(l1_cache, 0, sizeof(l1_cache)); //清楚L1缓存
#endif
}
check_func_result(hash_name, __FUNCTION__, sum, ts1);
}
一直在想如何做出一个比较客或符合实际的性能测试, 比如我ebench测试程序100%命中查找, 随机模拟偶尔刷掉L1/2级缓存, vList 输入数据是哈希ht_hash部分key数据(10%数据子集),有重复(不需要命中hash 中所有key)
emhash7 默认负载0.98 加入测试,效果还行
run_insert_random: jstd::flat16_hash_map<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 483.16 ms hashmap.find(key), check_sum: 81835627463776, average: 21.88 ns , time: 358.55 ms
run_insert_random: jstd::robin16_hash_map<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 699.13 ms hashmap.find(key), check_sum: 81835627463776, average: 21.84 ns , time: 357.79 ms
run_insert_random: ska::flat_hash_map<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 641.22 ms hashmap.find(key), check_sum: 81835627463776, average: 15.80 ns , time: 258.93 ms
run_insert_random: absl::flat_hash_map<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 503.19 ms hashmap.find(key), check_sum: 81835627463776, average: 19.41 ns , time: 317.99 ms
run_insert_random: emhash5::HashMap<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 756.88 ms hashmap.find(key), check_sum: 81835627463776, average: 13.40 ns , time: 219.49 ms
run_insert_random: emhash7::HashMap<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.937, time: 609.78 ms hashmap.find(key), check_sum: 81835627463776, average: 17.64 ns , time: 288.96 ms
run_insert_random: jstd::flat16_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 529.10 ms hashmap.find(key), check_sum: 133458100370101, average: 23.84 ns , time: 390.51 ms
run_insert_random: jstd::robin16_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 806.62 ms hashmap.find(key), check_sum: 133458100370101, average: 22.50 ns , time: 368.65 ms
run_insert_random: ska::flat_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1023.87 ms hashmap.find(key), check_sum: 133458100370101, average: 17.11 ns , time: 280.26 ms
run_insert_random: absl::flat_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 725.70 ms hashmap.find(key), check_sum: 133458100370101, average: 21.15 ns , time: 346.57 ms
run_insert_random: emhash5::HashMap<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1151.76 ms hashmap.find(key), check_sum: 133458100370101, average: 13.65 ns , time: 223.61 ms
run_insert_random: emhash7::HashMap<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.968, time: 800.35 ms hashmap.find(key), check_sum: 133458100370101, average: 18.97 ns , time: 310.86 ms
value 改成int了
run_insert_random: jstd::flat16_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 554.17 ms hashmap.find(key), check_sum: 133458100370101, average: 22.90 ns , time: 375.14 ms
run_insert_random: jstd::robin16_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 825.39 ms hashmap.find(key), check_sum: 133458100370101, average: 22.82 ns , time: 373.84 ms
run_insert_random: ska::flat_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1007.99 ms hashmap.find(key), check_sum: 133458100370101, average: 17.11 ns , time: 280.40 ms
run_insert_random: absl::flat_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 798.31 ms hashmap.find(key), check_sum: 133458100370101, average: 21.25 ns , time: 348.15 ms
run_insert_random: emhash5::HashMap<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 975.27 ms hashmap.find(key), check_sum: 133458100370101, average: 11.88 ns , time: 194.72 ms
run_insert_random: emhash7::HashMap<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.968, time: 759.62 ms hashmap.find(key), check_sum: 133458100370101, average: 18.04 ns , time: 295.54 ms
为什么在你的测试里,jstd::flat16_hash_map 这么快?是因为用 clang 编译的原因吗?在我两台云服务器里,jstd::flat16_hash_map 都是最慢的那一个。
为什么在你的测试里,jstd::flat16_hash_map 这么快?是因为用 clang 编译的原因吗?在我两台云服务器里,jstd::flat16_hash_map 都是最慢的那一个。
intel 9700k GCC 11.1 + wsl2 ubu2004
下面是 clang12 结果差不多 (云服务cpu 单核性能太低,但缓存大一些)
run_insert_random: jstd::flat16_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 642.42 ms hashmap.find(key), check_sum: 133458100370101, average: 22.55 ns , time: 369.53 ms
run_insert_random: jstd::robin16_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 929.18 ms hashmap.find(key), check_sum: 133458100370101, average: 28.73 ns , time: 470.77 ms
run_insert_random: ska::flat_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 963.37 ms hashmap.find(key), check_sum: 133458100370101, average: 16.29 ns , time: 266.95 ms
run_insert_random: absl::flat_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 713.61 ms hashmap.find(key), check_sum: 133458100370101, average: 21.24 ns , time: 347.92 ms
run_insert_random: emhash5::HashMap<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1053.21 ms hashmap.find(key), check_sum: 133458100370101, average: 12.68 ns , time: 207.76 ms
run_insert_random: emhash7::HashMap<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.968, time: 840.93 ms hashmap.find(key), check_sum: 133458100370101, average: 18.64 ns , time: 305.33 ms
//linux server Intel(R) Xeon(R) Silver 4210 CPU @ 2.20GHz gcc 7.5 ubu1604
run_insert_random: jstd::flat16_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1530.20 ms hashmap.find(key), check_sum: 133458100370101, average: 45.34 ns , time: 742.88 ms
run_insert_random: jstd::robin16_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1847.26 ms hashmap.find(key), check_sum: 133458100370101, average: 38.43 ns , time: 629.56 ms
run_insert_random: ska::flat_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 2146.28 ms hashmap.find(key), check_sum: 133458100370101, average: 33.75 ns , time: 552.90 ms
run_insert_random: absl::flat_hash_map<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1456.10 ms hashmap.find(key), check_sum: 133458100370101, average: 39.79 ns , time: 651.93 ms
run_insert_random: emhash5::HashMap<size_t, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 2007.20 ms hashmap.find(key), check_sum: 133458100370101, average: 24.60 ns , time: 403.11 ms
我也换成 GCC 11.1 了,刚开始看你用的 GCC 6.x,我也换成 GCC 6.x 测试一些可能版本不兼容的问题。
AMD EPYC 7K62 48核(2.6GHz,2核4G内存,Ubuntu 18.04,GCC 11.1):
DataSize = 16384000, test::MumHash
run_insert_random: jstd::flat16_hash_map<int, int> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 1680.51 ms hashmap.find(key), check_sum: 81835627463776, average: 50.34 ns, time: 824.71 ms
run_insert_random: jstd::robin16_hash_map<int, int> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 1089.93 ms hashmap.find(key), check_sum: 81835627463776, average: 30.84 ns, time: 505.28 ms
run_insert_random: jstd::robin_hash_map<int, int> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 1134.90 ms hashmap.find(key), check_sum: 81835627463776, average: 32.33 ns, time: 529.61 ms
run_insert_random: jstd::flat16_hash_map<int, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 2660.54 ms hashmap.find(key), check_sum: 133458100370101, average: 51.31 ns, time: 840.69 ms
run_insert_random: jstd::robin16_hash_map<int, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1279.80 ms hashmap.find(key), check_sum: 133458100370101, average: 34.15 ns, time: 559.44 ms
run_insert_random: jstd::robin_hash_map<int, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1333.04 ms hashmap.find(key), check_sum: 133458100370101, average: 34.12 ns, time: 558.97 ms
run_insert_random: jstd::flat16_hash_map<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 1808.19 ms hashmap.find(key), check_sum: 81835627463776, average: 50.73 ns, time: 831.09 ms
run_insert_random: jstd::robin16_hash_map<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 1177.34 ms hashmap.find(key), check_sum: 81835627463776, average: 32.06 ns, time: 525.34 ms
run_insert_random: jstd::robin_hash_map<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 1240.16 ms hashmap.find(key), check_sum: 81835627463776, average: 33.09 ns, time: 542.10 ms
run_insert_random: jstd::flat16_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 2863.93 ms hashmap.find(key), check_sum: 133458100370101, average: 51.69 ns, time: 846.85 ms
run_insert_random: jstd::robin16_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1451.17 ms hashmap.find(key), check_sum: 133458100370101, average: 34.37 ns, time: 563.17 ms
run_insert_random: jstd::robin_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1563.32 ms hashmap.find(key), check_sum: 133458100370101, average: 36.03 ns, time: 590.38 ms
编译器强烈推荐最新版本低1个大版本的,vs/clang/g++ 我一直使用最新的(几乎每天都要查看是否有新版本可更新:) gcc12+clang14+vs2022 )。主要是优化性能更好
云主机有可能和别人共享cpu ,测试有可能不准.
cmake 里面可以添加 flto 之类的优化 set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
云服务器还好,CPU基本是够用的,没那么忙,我看主要还是 CPU 架构的问题,但是我实在想不出来为什么会差这么多。。
这台 Intel 的云服务器三级缓存更小一点,性能更差。我懒得重新编译 hashmap_benchmark 了,ska::flat_hash_map,emhash5 与 jstd::robin_hash_map 的结果比较接近。
Intel Xeon Platinum 8255C 24核(2.5GHz,两核4GB,Ubuntu 20.04,GCC 11.1):
DataSize = 16384000, test::MumHash
run_insert_random: jstd::flat16_hash_map<int, int> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 2354.04 ms hashmap.find(key), check_sum: 81835627463776, average: 38.94 ns, time: 638.02 ms
run_insert_random: jstd::robin16_hash_map<int, int> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 1214.23 ms hashmap.find(key), check_sum: 81835627463776, average: 38.55 ns, time: 631.53 ms
run_insert_random: jstd::robin_hash_map<int, int> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 1237.25 ms hashmap.find(key), check_sum: 81835627463776, average: 43.52 ns, time: 712.99 ms
run_insert_random: jstd::flat16_hash_map<int, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 5699.52 ms hashmap.find(key), check_sum: 133458100370101, average: 42.21 ns, time: 691.54 ms
run_insert_random: jstd::robin16_hash_map<int, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1416.67 ms hashmap.find(key), check_sum: 133458100370101, average: 43.72 ns, time: 716.36 ms
run_insert_random: jstd::robin_hash_map<int, int> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1474.88 ms hashmap.find(key), check_sum: 133458100370101, average: 49.48 ns, time: 810.71 ms
run_insert_random: jstd::flat16_hash_map<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 2527.06 ms hashmap.find(key), check_sum: 81835627463776, average: 42.19 ns, time: 691.27 ms
run_insert_random: jstd::robin16_hash_map<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 1395.27 ms hashmap.find(key), check_sum: 81835627463776, average: 41.39 ns, time: 678.19 ms
run_insert_random: jstd::robin_hash_map<size_t, size_t> hashmap.size() = 7856563, cardinal = 9600000, load_factor = 0.468, time: 1429.24 ms hashmap.find(key), check_sum: 81835627463776, average: 44.20 ns, time: 724.16 ms
run_insert_random: jstd::flat16_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 6111.75 ms hashmap.find(key), check_sum: 133458100370101, average: 43.93 ns, time: 719.75 ms
run_insert_random: jstd::robin16_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1780.37 ms hashmap.find(key), check_sum: 133458100370101, average: 45.35 ns, time: 743.01 ms
run_insert_random: jstd::robin_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1818.44 ms hashmap.find(key), check_sum: 133458100370101, average: 48.73 ns, time: 798.40 ms
我的云服务器 AMD EPYC 7K62 48-Core Processor clang12 ubu2004
run_insert_random: std::unordered_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.660, time: 7709.28 ms hashmap.find(key), check_sum: 133458100370101, average: 53.04 ns , time: 869.03 ms
run_insert_random: jstd::flat16_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 3140.10 ms hashmap.find(key), check_sum: 133458100370101, average: 46.55 ns , time: 762.68 ms
run_insert_random: jstd::robin16_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1796.43 ms hashmap.find(key), check_sum: 133458100370101, average: 45.76 ns , time: 749.77 ms
run_insert_random: jstd::robin_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1555.68 ms hashmap.find(key), check_sum: 133458100370101, average: 30.48 ns , time: 499.32 ms
run_insert_random: ska::flat_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1629.39 ms hashmap.find(key), check_sum: 133458100370101, average: 25.78 ns , time: 422.44 ms
run_insert_random: emhash::HashMap<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1934.08 ms hashmap.find(key), check_sum: 133458100370101, average: 22.62 ns , time: 370.69 ms
run_insert_random: absl::flat_hash_map<size_t, size_t> hashmap.size() = 16244874, cardinal = 960000000, load_factor = 0.484, time: 1447.03 ms hashmap.find(key), check_sum: 133458100370101, average: 43.06 ns , time: 705.52 ms
嗯,和我结果差不多,CPU好像也一样,应该是也是腾讯的云服务器吧。
哦,还是有点区别的,clang 12 下,absl::flat_hash_map 结果还不错。
是的,只有腾讯云有这种AMD CPU 800RMB 3年用于编译测试 要测试各种组合 cpu+编译器+编译选项 我测试很多case,absl::flat_hash_map 总体还是最优的 你可以跑跑我目录下几个测试。 你的上面测试分别是插入和读取hit时间?
插入测试加一个reserve case。find 加一个find miss
在我的数独求解程序里,有这么尝试过,但我的经验是,编译器这个东西不好说:
https://github.com/shines77/gudoku https://github.com/shines77/gz_sudoku
总体上来说,clang 要强一点,这两个程序,第一个基本是 SIMD,第二个也有少量 SIMD。
我甚至还装了 Linux 下的 Intel C++ Compiler(现在叫 Intel oneAPI),有的编译器新版本的还不如旧一点的。clang 有些版本还有些莫名的 bug (比如匿名namespace下变慢),编译器也不是万能的,也不一定新的就一定快,要看代码吧,不过一般来说,clang > gcc > msvc,新版本可能快于旧版本。Intel C++ Compiler 是基于 clang 或 gcc 的,它已经完全没有任何优势了。
clang 对于 SIMD(Intel Intrinsics),是会调整 load,store 的指令顺序,还会更换更快的 SIMD 指令,但有时候可能会弄巧成拙。GCC 不会调整 load,store 的顺序,也不会改变和优化(Intel Intrinsics),但是 gcc 的编译选项是比 clang 更丰富,细节更多,可以说更稳健吧,clang 更激进一点。
cardinal_bench 是测试随机插入,插入的元素可能会重复,因为插入的同时也算做了一次 find(可能 hit,也可能 miss),所以我就也测了一次一模一样的 find_hit(),对比一下,两者时间相减大致可以理解为单纯插入的时间(虽然是不等价的),同时也可以观察一下 find_hit() 的性能。
find failed 就是 find miss 啊,表述不一样而已
https://github.com/martinus/map_benchmark 综合测试被我改成一个简单测试 容易直观对比各种hashmap性能 算是一个比较好的测试,你可以把你的实现放进去。
https://github.com/ktprime/emhash/blob/master/bench/martinus_bench.cpp
lighthouse@VM-8-17-ubuntu:~/emhash/bench$ ./mbench 2356
./test [2-9mptseb0d2 rjqf] n ankerl martinus dense emhash5 emhash5 emhash6 emhash6 emhash7 emhash7 emhash8 emhash8 emilib2 emilib2 emilib3 emilib3 cmd agrs = 23567 test hash: ankerl martinus dense emhash8 emhash8 bench_IterateIntegers map = emhash8 total iterate/removing time = 0.55, 1.66|83332083325000
bench_IterateIntegers map = martinus dense total iterate/removing time = 0.56, 1.66|83332083325000
bench_randomFindString map = emhash8 0% 13 byte success time = 1.56 s 3094 loadf = 0.48 25% 13 byte success time = 1.91 s 12501887 loadf = 0.48 50% 13 byte success time = 2.02 s 25000807 loadf = 0.48 75% 13 byte success time = 2.02 s 37499823 loadf = 0.48 100% 13 byte success time = 1.93 s 49998809 loadf = 0.48 0% 100 byte success time = 2.46 s 1154 loadf = 0.76 25% 100 byte success time = 3.42 s 24997607 loadf = 0.76 50% 100 byte success time = 3.53 s 49995539 loadf = 0.76 75% 100 byte success time = 3.03 s 74992241 loadf = 0.76 100% 100 byte success time = 3.29 s 99989650 loadf = 0.76 total time = 9.45 + 15.74 = 25.19
bench_randomFindString map = martinus dense 0% 13 byte success time = 1.53 s 3094 loadf = 0.48 25% 13 byte success time = 1.92 s 12501887 loadf = 0.48 50% 13 byte success time = 2.10 s 25000807 loadf = 0.48 75% 13 byte success time = 2.29 s 37499823 loadf = 0.48 100% 13 byte success time = 2.32 s 49998809 loadf = 0.48 0% 100 byte success time = 2.91 s 1154 loadf = 0.76 25% 100 byte success time = 3.91 s 24997607 loadf = 0.76 50% 100 byte success time = 4.17 s 49995539 loadf = 0.76 75% 100 byte success time = 4.57 s 74992241 loadf = 0.76 100% 100 byte success time = 4.68 s 99989650 loadf = 0.76 total time = 10.16 + 20.25 = 30.41
bench_randomEraseString map = emhash8 000000000001ffff time = 3.73, loadf = 0.50 65541 00000000000fffff time = 5.34, loadf = 0.50 523526 00000000000fffff time = 4.24, loadf = 0.50 523526 00000000000fffff time = 5.39, loadf = 0.50 523526 000000000007ffff time = 5.33, loadf = 0.50 262059 total time = 24.09 s
bench_randomEraseString map = martinus dense 000000000001ffff time = 2.73, loadf = 0.50 65541 00000000000fffff time = 5.13, loadf = 0.50 523526 00000000000fffff time = 4.95, loadf = 0.50 523526 00000000000fffff time = 5.13, loadf = 0.50 523526 000000000007ffff time = 4.75, loadf = 0.50 262059 total time = 22.75 s ....
已经可以在 clang
下编译了,并且 CMakeLists.txt
中已添加 LPO/LTO
的支持,由于开启 LPO/LTO
在 gcc
下无法编译成功,所以只在 clang
中开启。使用 gcc
或 clang
,或者是否开启 LPO/LTO
,的确跟我估计的一样,有的变快了,有点变慢了,很难确定,相对来说还是 gcc
更稳定一点。
由于 LPO/LTO
是从 CMake 3.9 才开始支持,所以 CMakeLists.txt
里有完整的版本判断和处理流程,以自适应不同的 CMake 版本,同时也支持手动添加 LPO/LTO
编译选项,具体可以参阅 CMakeLists.txt
中的源码。
在 clang
编译的方法:
cd ./clang
./cmake_config.sh
make
或者在./clang
下直接执行命令:
cmake -G "Unix Makefiles" -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DABSL_BUILD_TESTING=OFF -DABSL_USE_GOOGLETEST_HEAD=OFF -DABSL_PROPAGATE_CXX_STD=ON -DCMAKE_PREFIX_PATH=../install ./
同时,编写了一个清除 cmake 编译缓存的脚本:cmake_clean.sh
,用于更换编译器版本,或者编译选项时,清除 CMake 的缓存。
我在大型程序中使用lto 有比较大的性能10-20%
生成可执行程序 cmakelist.txt 加如下, cmake 升级到版本高一点。我喜欢用最新的
set(CMAKE_CXX_FLAGS "-ffunction-sections -fdata-sections -flto")
if (CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "-ffunction-sections -fdata-sections -flto -fwhole-program")
endif()
关于测试数据随机性, 没必要固定随机种子,希望每次数据都不一样
int main(int argc, char * argv[])
{
jstd::RandomGen RandomGen(time(0));
jstd::MtRandomGen mtRandomGen(time(0));
...
}
目前看你的实现 大量小数据 sizeof(k+v) < 20 插入方面有些优势,你的hashmap使用上能否更简单,直接include 单个头文件不需要其他(一堆宏和其他文件),最近想把clickhouse hashmap加入测试,发现接口和std 差异太大 。。。
测了一个hashset
你应该说的是插入大量数据的时候,但目前 jstd::robin_hash_map 插入 “大对象” 的时候是很慢的,我本来想放到以后再实现的,但是我在写一篇 "怎样写一个高性能的哈希表?" 的文章,觉得还是很要先实现一部分 ”大对象“ 的处理逻辑,原理类似跟 https://github.com/martinus/unordered_dense 类似。
一直想回复你的,但一开始是在忙于大修改 jstd_hashmap,半个月前基本告一段落,有时间回复了,又一直忘记。
我最近在忙于写这篇文章 怎样写一个高性能的哈希表?,进展很慢,我写了可能快一个星期了,我真正想写的内容连切入都还没开始切入,可能还是我的命题起得太大了。写出来,很多东西跟我当初设想的很不一样,甚至可以说之前做的所有工作基本算是白费的,虽然已经有一个很好的积累,有这个积累才能更好实现后面的设想,但是。。。
其实我早就知道,虽然测试哈希表 <int, int>, <uint64_t, uint64_t> 这种 KV 也有一定的现实意义,但其实我心里是很清楚,哈希表在现实应用中,更有实际价值的还是 <string, string>, <string, uint64_t> 这种 “中对象” KV。
为此,在很多天前,我已经在对 /bench/time_hash_map 进行了大改,命名为 time_hash_map_new,因为原来 time_hash_map 定义的用来测试的 HashObject<T, Size, HashSize> 对象设计得还是有点蠢的,我在给 jstd::robin_hash_map 添加 kNeedStoreHash 功能的时候意识到了这一点,因为它即跟现实的应用很不一样,不太具有现实意义,但它测的对象其实是 HashObject<int, 4, 4> 和 HashObject<uint64_t, 8, 8>,虽然测试的结果跟测试<int, int>, <uint64_t, uint64_t> 的结果是基本一致的,但是很不科学。
给 time_hash_map_new 换了个壳,改为测试 <int, int>, <uint64_t, uint64_t>,<std::string, std::string>, <std::string_view, std::string_view>之后就没管了,<std::string, std::string>的代码一直并未实现(因为实现起来要做很多修改)。今天早上,我一咬牙,决定先暂停写那篇文章,把 <std::string, std::string>的测试代码写出来了,跟 <int, int>, <uint64_t, uint64_t> 的测试还是很不一样的,<std::string_view, std::string_view> 我晚一点再实现,虽然需要做的改动并不大。
其实,我想尽快把 time_hash_map_new 移植到我的 hashmap_benchmark 里的,想看看别的和别的哈希表的比较数据(虽然我知道其实和原来 time_hash_map 里面测试大对象 HashObject<size_t, 256, 32> 的结果出入不会很大),但是早上有点困,我就睡了一下,刚起来。
怎样写一个高性能的哈希表? 这篇文章你真的可以看一看,以后我都会围绕这篇文章来做事情,虽然很多东西还没开始写,我真正的目的是要介绍一下,absl::flat_hash_map(Swiss Table)的演变,原理,优点和缺点;robin hood哈希表的原理,优点和缺点;布谷鸟哈希表的原理,优点和缺点。
我的 jstd::robin_hash_map 距离上一次的版本,已经做了很大的改进,以及很多的改动。
https://github.com/martinus/unordered_dense 的原理和工作方式,我也理解了,很难评价它的方式是传统的 robin hood 哈希表是好还是坏,但是他这种方式,在 find (包括 find hit 和 find miss)的时候,有可能更快。他的原理是,除了对 distance 排序,同时也顺便对 ctrl_hash 按升序或降序排序,这样即使搜索落在同一个 bucket 的数据时,不一定要扫描到终止点,也能提前知道扫描完了,因为除了 distance 是有序的,ctrl_hash 也是有序的。但是这样做的缺点就是,插入数据的时候,一旦找到插入的点,之后的数据就必须全部往后移动,以保持 ctrl_hash 值的有序性,这会导致移动的元素要比传统的 robin hood 哈希表多,虽然移动的只是 entry 的索引值 index 。
我的 jstd::robin_hash_map 做的改进:
absl::flat_hash_map
的 PolicyTraits
和 Allocator
那一套,我也移植过来了,这样更完整,但是它的 emplace(....) 中 extract Key 的那一套(absl::apply(F, ...), absl::DecomposePair 等),我没有用,以后有机会再写一个跟它一样处理方式的版本。它利用 std::tuple 和 absl::apply,在静态编译阶段有可能跳过一些不必要的转换获取 key 或跟 key 等价的类型的值,有时候能提高性能。哦,我的 hashmap 是否可以写成一个文件的,理论上当然是可以,但是这样代码并不是很好看,理论上,只要是 header only 的,都可以浓缩到一个头文件里的。原则上一般不会这么做,但如果确实有需求,可以弄一下。目前,我的哈希表都是 header only 的啊,只需要引入 submodule 或者把整个 /jstd 目录复制过去就可以了,不需要配置,也不需要写 CMakeLists.txt。他只不过是一个文件和多个文件的区别。
不过,今天早上测试 /bench/time_hash_map_new
的 <std::string, std::string> 的时候,我发现 jstd::robin_hash_map 和 jstd::v3::robin_hash_map 可能是存在 bug 的,因为 emplace_and_replace 的时候,哈希表的 load_factoer() 好像比单纯 emplace() 时的大,说明 size() 比正常的时候大。
clickhouse hashmap 在我写 cardinal_bench 参考的那篇文章里是有看到,但是我也没准备去弄,那篇文章有提到,他自己整理和提取了一个版本,但是我在 github, gitee 上没看到他修改的版本,如果有直接可用的版本,我就会试一下的。clickhouse hashmap 很早之前我看过一下的,接口都是他们自己弄的,它只需要方便处理他们的 SQL 语句就行,所以没有遵循 C++ 标准库的接口。
哦,说到这里,我想起了我的 jstd::robin_hash_map 另一个改进,就是用 prefetch 指令在 rehash() 的时候,对旧的 slot 做了一个读的 prefetch,本来 prefetch 最大的价值就是对新的 slot 做写的 prefetch,但目前我也还没有写,因为工程量比 读 prefetch 的要大很多,而且并不一定有很大的效果。实践下来,对旧的 slot 做读的 prefetch,提升是很微小的。。。
absl::flat_hashmap 只对 ctrl 这个指针在 find() 开始的时候,做了一次 prefetch_T2,意义也基本不大(甚至有一点点变慢,不同CPU效果不一样),他写了更完整的 prefetch 代码,但没有使用,因为确实一般情况下,用不了。唯一能用的也只有 rehash 的时候,但 Google 没有在 rehash 中使用 prefetch。
我之所以会想起来这个,是因为你提到的 clickhouse hashmap,让我想起来了我写 cardinal_bench 参考的那篇文章里,有使用 prefetch 技术,在数据量很大的时候,最终达到和 clickhouse hashmap 差不多,或更快的速度。
目前看 prefetch对hashmap 似乎没太大作用,即便rehash 过程线性遍历未必有多大收益。要配合循环展开特殊应用场景行 字符串hashmap提升性能有一定难度,相当部分消耗hash计算和内存分配(配合定制内存池和保留字符串, hash 可以提升性能)
哦,另外,我的 hashmap_benchmark,原来采用的是 C++14,但是 CMakeLists.txt 里有的地方写的 C++11,导致最终还是变成了以 C++11 的方式编译的,这个 bug 后来我发现和修正了。再后来,我干脆改成了 C++17。改成 C++ 14以后,gcc 的测试结果有所提升,改为 C++17 相比 C++14 好像提升不大。
/clang/CMakeLists.txt 中我还尝试了改为 llvm/clang 的 C++ 库 libc++,感觉变化不大,我又改回了 g++ 的 libstdc++。两者肯定是有差别的,但我没仔细分辨两者的差别。
此外,我还把你的 emhash7 也加入了 cardinal_bench 和 time_hash_map 测试,但由于在 time_hash_map 在测试 16 字节以上的大对象是,可能有bug,很慢很慢,我就在 time_hash_map 关掉了 emhash7 的测试。
另外,我的 hashmap,一般所用到的宏都写在 hashmap 头文件里,不会依赖别的头文件中的宏。如果说 /basic/stddef.h,/config/config.h 这种做基本配置文件中的宏也算的话,那还是有,但是它是无害的,没有这些配置文件应该也不影响使用,一般可能有 likely(), unlikely() 这些宏,至于 config.h,这个一般的库都是需要的,而且我的 hashmap 也不会直接依赖 config.h,但是 /basic/stddef.h 可能是有依赖的,刚才想了一下,除了 likely(),还有 JSTD_FORECE_INLINE,JSTD_NO_INLINE 这种宏,基本就没了。
目前看 prefetch对hashmap 似乎没太大作用,即便rehash 过程线性遍历未必有多大收益。要配合循环展开特殊应用场景行 字符串hashmap提升性能有一定难度,相当部分消耗hash计算和内存分配(配合定制内存池和保留字符串, hash 可以提升性能)
没有,rehash 还是有收益的,你说的线性遍历是对于读的,或者某些线性的哈希函数,比如 gcc 下的 std::hash
rehash 利用 prefetch 的方式是这样的,对于写,先批量取出一堆 slot,比如 128 个,计算他们的 key ,对应的哈希值和 index(并保存到数组中),得到将会可能写到的位置,用 prefetch 指令先写一下(不支持写就读一下),ARM上支持 prefetch write (gcc上支持)。然后利用已经计算出来的 hash 值和 index,开始搬迁旧 slot 到新 slot。
在我前面提到过的,我是看了这篇文章才写的 cardinal_becnh:HashTable性能测试(CK/phmap/ska)
这篇文章里面就提到了 prefetch 技术,虽然它没有具体的实现代码,但是你看这个测试代码,应该就能看得出来个大概:
static void run_insert_precompute(benchmark::State & state) {
// Code inside this loop is measured repeatedly
std::vector<int> a;
ConstructRandomSet(state.range(0), state.range(1), a);
static constexpr size_t PREFETCH = 16;
std::vector<size_t> hash_values(BLOCK);
for (auto _ : state) {
HashSet<int> hs;
const auto* data = a.data();
const size_t size = a.size();
for (size_t i = 0; i < size; i += BLOCK) {
for (size_t j = 0; j < BLOCK; j++) {
size_t hashval = hs.hash_function()(data[i + j]);
hash_values[j] = hashval;
}
for (size_t j = 0, k = PREFETCH; j < BLOCK; j++, k++) {
if (k < BLOCK) {
hs.prefetch_hash(hash_values[k]);
}
hs.emplace_with_hash(hash_values[j], data[i + j]);
}
}
HSINFO(__func__);
}
}
prefetch 的难点在于,提前量设置为多大,batch 长度取多大(对于写 prefetch,batch 的长度基本就等价于提前量,加上 pre_compute 的数据量,就是在给 CPU 去完成 prefetch 的 hint 。但是缺点就是,哈希值保存到数组,也是会进入缓存的,所以 prefetch 的提升必须要弥补这个损失,不然就会变成负优化,而且还徒增了很多本不需要的运算时间,也需要算在 prefetch 的成本里。
在这篇文章里,他说 prefetch 还是有效果的。(我才发现,其实你前面说的,可能已经是这篇文章,已经看到你在下面的回复了,没想到这个 blog 还是可以回复的啊。。。)
原来我说的,你都知道了的。。。
你可以这么理解,他那篇文章是在插入的时候,使用了 prefetch 有收益。那么 rehash() 其实就是一个批量插入的过程,所以也就可以利用同样的 prefetch 技术了。
之前我跟你说过,在一个国产的叫 MatrixOne 的数据库(go语言写的),就使用了 batchInsert() 接口,他没有用 prefetch,但是他利用 SIMD 的 crc32 和 AES 指令,批量计算的时候,总体效率更高(crc32指令在 Intel 上的吞吐量是 1,延迟是 3,也就是说 3 条 crc32 指令连续执行,但只需要 5 个时钟周期。但 crc32指令在 AMD CPU上,吞吐量是 3,延迟是 3,没有任何优化价值),也是把哈希值先保存到数组,再批量插入,但他没做 prefetch 。
另外,还有一个问题,最近我的香港的VPS IP好像被band了,我的科学上网出了点问题,github 上的图片,以 user-images.githubusercontent.com 域名开头的图片均访问不了,所以图片我都看不到。
你可以看代码
// Issues CPU prefetch instructions for the memory needed to find or insert
// a key. Like all lookup functions, this support heterogeneous keys.
//
// NOTE: This is a very low level operation and should not be used without
// specific benchmarks indicating its importance.
void prefetch_hash(size_t hashval) const {
(void)hashval;
#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86))
auto seq = probe(hashval);
_mm_prefetch((const char *)(ctrl_ + seq.offset()), _MM_HINT_NTA);
_mm_prefetch((const char *)(slots_ + seq.offset()), _MM_HINT_NTA);
#elif defined(__GNUC__)
auto seq = probe(hashval);
__builtin_prefetch(static_cast<const void*>(ctrl_ + seq.offset()));
__builtin_prefetch(static_cast<const void*>(slots_ + seq.offset()));
#endif // __GNUC__
}
template <class K = key_type>
void prefetch(const key_arg<K>& key) const {
prefetch_hash(this->hash(key));
}
他这个代码没什么特别的啊,基本是照抄 absl::flat_hash_map 的,就是利用 hash 值,计算出 ctrl 和 slot 的位置,读一下。其实 GCC 下的 __builtin_prefetch() 是支持 prefetch write 的,他没有利用这个特性,这会让 __builtin_prefetch() 在 ARM 等弱内存一致性的 CPU 上调用 prefetch write 指令,更准确一点。
我大致看了一下,这个 /parallel_hashmap/phmap.h
,其实基本上是照抄的 absl::flat_hash_map,你基本上可以认为他是 absl::flat_hash_map 改进版,因为 absl::flat_hash_map 有这段相似的 prefetch 代码,但不是针对 hash 值的,并且也没有真正的用上。当然,也有可能是 absl::flat_hash_map 抄他的,不过我觉得可能性不太大。
absl::flat_hash_map 中的代码是这样的:
// Issues CPU prefetch instructions for the memory needed to find or insert
// a key. Like all lookup functions, this support heterogeneous keys.
//
// NOTE: This is a very low level operation and should not be used without
// specific benchmarks indicating its importance.
template <class K = key_type>
void prefetch(const key_arg<K>& key) const {
(void)key;
// Avoid probing if we won't be able to prefetch the addresses received.
#ifdef ABSL_INTERNAL_HAVE_PREFETCH
prefetch_heap_block();
auto seq = probe(ctrl_, hash_ref()(key), capacity_);
base_internal::PrefetchT0(ctrl_ + seq.offset());
base_internal::PrefetchT0(slots_ + seq.offset());
#endif // ABSL_INTERNAL_HAVE_PREFETCH
}
是不是很像?
而且我发现一个错误,phmap.h
中 prefetch 的用法,在 MSVC 中的用法是错误的:
#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86))
auto seq = probe(hashval);
_mm_prefetch((const char *)(ctrl_ + seq.offset()), _MM_HINT_NTA);
_mm_prefetch((const char *)(slots_ + seq.offset()), _MM_HINT_NTA);
#else
........
#endif
_MM_HINT_NTA
的意思是不经过缓存,使用 DMA 的方式(Directly Memory Access),直接写到内存,这不会有任何的 prefetch 效果。。。应该使用 _MM_HINT_T0,或者 _MM_HINT_T1,_MM_HINT_T2,一般使用 T0,因为既然我们要预取,肯定是尽量要预取到 L1 缓存中,如果提前量不够,可以加大 batch_size 。
昨天下午,我已经把 jstd::robin_hash_map 的 bug 修复了,是一个地方的 swap(obj1, obj2); obj1, obj2 都是指针,所以应该写成 swap(obj1, obj2);
并且我也把 time_hash_map_new 移植到 hashmap_benchmark 中了,测了一下,结果出乎我的意料之外。
jstd::robin_hash_map 在原来的 time_hash_map 中测试 HashObject<size_t, 256, 64> 中(256Bytes 的大对象)是比 absl::flat_hasp_map 性能差很多的,但是在 time_hash_map_new 中测试 hashmap<std::string, std::string> 性能目前却是最好的,这还是未实现 https://github.com/martinus/unordered_dense 这种用索引指向 slot 数组的方式的情况下,如果实现了,性能会更好。
AMD EPYC 7K62 48核(2.6GHz,2核4G内存,Ubuntu 18.04,GCC 11.1)
hashmap<std::string, std::string> benchmark:(Key是 5-31 个字符的随机字符串,Value 是 1-31 字符的随机字符串)
jstd::robin_hash_map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 139.90 ns lf=0.298
random_find_success 177.33 ns lf=0.298
find_failed 46.83 ns lf=0.298
find_empty 48.97 ns lf=0.000
insert 527.94 ns lf=0.298 300.4 MB (+128.0 MB)
insert_predicted 386.38 ns lf=0.298 300.4 MB (+127.9 MB)
insert_replace 160.84 ns lf=0.298 428.4 MB
emplace 540.46 ns lf=0.298 300.4 MB (+128.0 MB)
emplace_predicted 381.43 ns lf=0.298 300.4 MB (+127.9 MB)
emplace_replace 165.11 ns lf=0.298 428.4 MB
try_emplace 544.64 ns lf=0.298 300.4 MB (+128.0 MB)
try_emplace_predicted 385.99 ns lf=0.298 300.4 MB (+127.9 MB)
try_emplace_replace 195.46 ns lf=0.298 428.4 MB
operator [] 546.46 ns lf=0.298 300.4 MB (+128.0 MB)
operator [] predicted 386.87 ns lf=0.298 300.4 MB (+127.9 MB)
operator [] replace 206.42 ns lf=0.298 428.4 MB
serial_erase 234.43 ns lf=0.000 428.4 MB
random_erase 292.05 ns lf=0.000 428.4 MB
erase_failed 50.67 ns lf=0.298 428.4 MB
toggle 106.22 ns lf=0.000 300.4 MB
iterate 19.06 ns lf=0.298 428.4 MB
ska::flat_hash_map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 137.20 ns lf=0.298
random_find_success 173.51 ns lf=0.298
find_failed 93.48 ns lf=0.298
find_empty 83.25 ns lf=0.000
insert 732.67 ns lf=0.298 303.4 MB (+144.0 MB)
insert_predicted 490.44 ns lf=0.298 303.4 MB (+144.0 MB)
insert_replace 141.13 ns lf=0.298 447.4 MB
emplace 725.69 ns lf=0.298 303.4 MB (+144.0 MB)
emplace_predicted 489.52 ns lf=0.298 303.4 MB (+144.0 MB)
emplace_replace 143.46 ns lf=0.298 447.4 MB
try_emplace 745.67 ns lf=0.298 303.4 MB (+144.0 MB)
try_emplace_predicted 501.55 ns lf=0.298 303.4 MB (+144.0 MB)
try_emplace_replace 179.92 ns lf=0.298 447.4 MB
operator [] 741.91 ns lf=0.298 303.4 MB (+144.0 MB)
operator [] predicted 504.52 ns lf=0.298 303.4 MB (+144.0 MB)
operator [] replace 179.20 ns lf=0.298 447.4 MB
serial_erase 231.00 ns lf=0.000 447.4 MB
random_erase 278.45 ns lf=0.000 447.4 MB
erase_failed 87.53 ns lf=0.298 447.4 MB
toggle 96.52 ns lf=0.000 303.4 MB
iterate 16.59 ns lf=0.298 447.4 MB
emhash5::HashMap<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 140.68 ns lf=0.596
random_find_success 175.08 ns lf=0.596
find_failed 95.09 ns lf=0.596
find_empty 74.65 ns lf=0.000
insert 676.71 ns lf=0.596 303.4 MB (+72.0 MB)
insert_predicted 375.84 ns lf=0.596 303.4 MB (+71.9 MB)
insert_replace 163.44 ns lf=0.596 375.4 MB
emplace 666.75 ns lf=0.596 303.4 MB (+72.0 MB)
emplace_predicted 381.48 ns lf=0.596 303.4 MB (+71.9 MB)
emplace_replace 167.82 ns lf=0.596 375.4 MB
try_emplace 724.13 ns lf=0.596 303.4 MB (+72.0 MB)
try_emplace_predicted 377.04 ns lf=0.596 303.4 MB (+71.9 MB)
try_emplace_replace 200.57 ns lf=0.596 375.4 MB
operator [] 694.53 ns lf=0.596 303.4 MB (+72.0 MB)
operator [] predicted 393.05 ns lf=0.596 303.4 MB (+71.9 MB)
operator [] replace 217.85 ns lf=0.596 375.4 MB
serial_erase 244.54 ns lf=0.000 375.4 MB
random_erase 279.51 ns lf=0.000 375.4 MB
erase_failed 94.86 ns lf=0.596 375.4 MB
toggle 94.48 ns lf=0.000 303.4 MB
iterate 9.65 ns lf=0.596 375.4 MB
absl::flat_hash_map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 214.47 ns lf=0.596
random_find_success 259.89 ns lf=0.596
find_failed 75.84 ns lf=0.596
find_empty 65.41 ns lf=0.000
insert 690.03 ns lf=0.596 303.4 MB (+65.0 MB)
insert_predicted 400.56 ns lf=0.596 303.4 MB (+65.0 MB)
insert_replace 236.83 ns lf=0.596 368.4 MB
emplace 692.79 ns lf=0.596 303.4 MB (+65.0 MB)
emplace_predicted 399.18 ns lf=0.596 303.4 MB (+65.0 MB)
emplace_replace 239.47 ns lf=0.596 368.4 MB
try_emplace 695.86 ns lf=0.596 303.4 MB (+65.0 MB)
try_emplace_predicted 406.26 ns lf=0.596 303.4 MB (+65.0 MB)
try_emplace_replace 284.39 ns lf=0.596 368.4 MB
operator [] 698.35 ns lf=0.596 303.4 MB (+65.0 MB)
operator [] predicted 411.84 ns lf=0.596 303.4 MB (+65.0 MB)
operator [] replace 269.57 ns lf=0.596 368.4 MB
serial_erase 304.86 ns lf=0.000 368.4 MB
random_erase 367.97 ns lf=0.000 368.4 MB
erase_failed 76.40 ns lf=0.596 368.4 MB
toggle 200.27 ns lf=0.000 303.4 MB
iterate 6.12 ns lf=0.596 368.4 MB
测试使用emhash8(针对大 key+value),emhash5针对小数据,ska和你的hashmap load_factor 小一点性能要好一些 可以更新我的git同步一下代码我也测试看看
hashmap_benchmark
我新加了三个哈希表:
tsl::robin_map
: https://github.com/Tessil/robin-map.gitrobin_hood::unordered_flat_map
: https://github.com/martinus/robin-hood-hashing.gitankerl::unordered_dense::map
: https://github.com/martinus/unordered_dense.gittime_hash_map_new
中也加入以上3个哈希表,以及 emhash8
的测试,emhash8
和 jstd::robin_hash_map
差不多,ankerl::unordered_dense::map
毫无疑问现在是最快的,我已经在写使用 index 的版本了,不过复杂度不低,把所有逻辑揉在一起工作量有点高,单独写要简单一些,但是写一个完全自适应的版本也是我的初衷,C++版的目前我还没看到过。如果要说有, go 语言的 map 算是,但是 go 语言的版本我觉得写得不够好,而且 go 的编译器跟 C++ 还有点差距。
如果只想测试 <std::string, std::string>,可以使用这样的命令行参数,节约时间一点:
./bin/time_hash_map_new string
测试结果:
AMD EPYC 7K62 48核(2.6GHz,2核4G内存,Ubuntu 18.04,GCC 11.1)
jstd::robin_hash_map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 145.39 ns lf=0.298
random_find_success 174.25 ns lf=0.298
find_failed 52.15 ns lf=0.298
find_empty 48.91 ns lf=0.000
insert 539.48 ns lf=0.298 300.5 MB (+128.0 MB)
insert_predicted 380.29 ns lf=0.298 300.5 MB (+127.9 MB)
insert_replace 165.20 ns lf=0.298 428.5 MB
emplace 540.17 ns lf=0.298 300.5 MB (+128.0 MB)
emplace_predicted 383.57 ns lf=0.298 300.5 MB (+127.9 MB)
emplace_replace 166.11 ns lf=0.298 428.5 MB
try_emplace 533.87 ns lf=0.298 300.5 MB (+128.0 MB)
try_emplace_predicted 371.17 ns lf=0.298 300.5 MB (+127.9 MB)
try_emplace_replace 199.15 ns lf=0.298 428.5 MB
operator [] 529.49 ns lf=0.298 300.5 MB (+128.0 MB)
operator [] predicted 379.41 ns lf=0.298 300.5 MB (+127.9 MB)
operator [] replace 205.80 ns lf=0.298 428.5 MB
serial_erase 248.11 ns lf=0.000 428.5 MB
random_erase 288.34 ns lf=0.000 428.5 MB
erase_failed 43.40 ns lf=0.298 428.5 MB
toggle 99.15 ns lf=0.000 300.5 MB
iterate 19.60 ns lf=0.298 428.5 MB
emhash8::HashMap<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 120.61 ns lf=0.596
random_find_success 221.26 ns lf=0.596
find_failed 77.87 ns lf=0.596
find_empty 63.29 ns lf=0.000
insert 568.12 ns lf=0.596 321.3 MB (+37.9 MB)
insert_predicted 389.20 ns lf=0.596 321.3 MB (+37.9 MB)
insert_replace 179.74 ns lf=0.596 359.2 MB
emplace 570.37 ns lf=0.596 321.3 MB (+37.9 MB)
emplace_predicted 391.85 ns lf=0.596 321.3 MB (+37.9 MB)
emplace_replace 174.36 ns lf=0.596 359.2 MB
try_emplace 565.84 ns lf=0.596 321.3 MB (+37.9 MB)
try_emplace_predicted 389.64 ns lf=0.596 321.3 MB (+37.9 MB)
try_emplace_replace 291.43 ns lf=0.596 359.2 MB
operator [] 567.85 ns lf=0.596 321.3 MB (+37.9 MB)
operator [] predicted 387.62 ns lf=0.596 321.3 MB (+37.9 MB)
operator [] replace 215.08 ns lf=0.596 359.2 MB
serial_erase 235.95 ns lf=0.000 359.2 MB
random_erase 443.07 ns lf=0.000 359.2 MB
erase_failed 62.27 ns lf=0.596 359.2 MB
toggle 99.63 ns lf=0.000 321.3 MB
iterate 3.57 ns lf=0.596 359.2 MB
ankerl::unordered_dense::map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 113.47 ns lf=0.596
random_find_success 195.19 ns lf=0.596
find_failed 41.23 ns lf=0.596
find_empty 2.83 ns lf=0.000
insert 378.61 ns lf=0.596 303.7 MB (+37.9 MB)
insert_predicted 333.75 ns lf=0.596 303.7 MB (+42.9 MB)
insert_replace 225.93 ns lf=0.596 346.6 MB
emplace 465.11 ns lf=0.596 308.7 MB (+37.9 MB)
emplace_predicted 331.37 ns lf=0.596 308.7 MB (+37.9 MB)
emplace_replace 243.27 ns lf=0.596 346.6 MB
try_emplace 468.07 ns lf=0.596 308.7 MB (+37.9 MB)
try_emplace_predicted 331.42 ns lf=0.596 308.7 MB (+37.9 MB)
try_emplace_replace 411.04 ns lf=0.596 346.6 MB
operator [] 460.16 ns lf=0.596 308.7 MB (+37.9 MB)
operator [] predicted 337.43 ns lf=0.596 308.7 MB (+37.9 MB)
operator [] replace 141.90 ns lf=0.596 346.6 MB
serial_erase 217.92 ns lf=0.000 346.6 MB
random_erase 455.44 ns lf=0.000 346.6 MB
erase_failed 44.21 ns lf=0.596 346.6 MB
toggle 90.80 ns lf=0.000 308.7 MB
iterate 3.59 ns lf=0.596 346.6 MB
tsl::robin_map
, robin_hood::unordered_flat_map
在测试 <std::string, std::string> 时性能一般,我就不贴了,有兴趣你可以测一下。
测试数据按hashmap 横向对比输出比较直观(需预先存,按接口输出), load factor 是影响性能的重要因素,可以设置相同的最大 max_load_factor 再对比(我改其他hashmap 设置相同load_factor 对比 按absl 默认7/8), 换一个编译器在其他cpu测试结果也可能不同
intel 9700 clang 12 刚才把你hash map 负载设置0.8 测试卡主不动 你的实现不错加油小伙子
jstd::robin_hash_map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 90.50 ns lf=0.298
random_find_success 122.97 ns lf=0.298
find_failed 29.75 ns lf=0.298
find_empty 28.50 ns lf=0.000
insert 378.09 ns lf=0.298 300.8 MB (+128.0 MB)
insert_predicted 237.47 ns lf=0.298 300.8 MB (+127.8 MB)
insert_replace 93.04 ns lf=0.298 428.8 MB
emplace 436.86 ns lf=0.298 300.8 MB (+128.0 MB)
emplace_predicted 239.01 ns lf=0.298 300.8 MB (+127.8 MB)
emplace_replace 95.98 ns lf=0.298 428.8 MB
try_emplace 375.62 ns lf=0.298 300.8 MB (+128.0 MB)
try_emplace_predicted 228.17 ns lf=0.298 300.8 MB (+127.8 MB)
try_emplace_replace 128.47 ns lf=0.298 428.8 MB
operator [] 464.22 ns lf=0.298 300.8 MB (+128.1 MB)
operator [] predicted 309.80 ns lf=0.298 300.9 MB (+127.8 MB)
operator [] replace 179.69 ns lf=0.298 428.9 MB
serial_erase 157.19 ns lf=0.000 428.9 MB
random_erase 239.81 ns lf=0.000 428.9 MB
erase_failed 27.76 ns lf=0.298 428.9 MB
toggle 77.09 ns lf=0.000 300.9 MB
iterate 9.97 ns lf=0.298 428.9 MB
emhash8::HashMap<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 74.75 ns lf=0.596
random_find_success 141.49 ns lf=0.596
find_failed 41.38 ns lf=0.596
find_empty 31.49 ns lf=0.000
insert 441.69 ns lf=0.596 321.7 MB (+38.0 MB)
insert_predicted 285.13 ns lf=0.596 321.7 MB (+38.0 MB)
insert_replace 114.39 ns lf=0.596 359.7 MB
emplace 467.85 ns lf=0.596 321.7 MB (+38.0 MB)
emplace_predicted 277.92 ns lf=0.596 321.7 MB (+38.0 MB)
emplace_replace 117.26 ns lf=0.596 359.7 MB
try_emplace 447.40 ns lf=0.596 321.7 MB (+38.0 MB)
try_emplace_predicted 273.30 ns lf=0.596 321.7 MB (+38.0 MB)
try_emplace_replace 179.50 ns lf=0.596 359.7 MB
operator [] 500.93 ns lf=0.596 321.7 MB (+38.0 MB)
operator [] predicted 331.30 ns lf=0.596 321.7 MB (+38.0 MB)
operator [] replace 182.84 ns lf=0.596 359.7 MB
serial_erase 156.04 ns lf=0.000 359.7 MB
random_erase 314.11 ns lf=0.000 359.7 MB
erase_failed 42.15 ns lf=0.596 359.7 MB
toggle 78.80 ns lf=0.000 321.7 MB
iterate 2.91 ns lf=0.596 359.7 MB
ankerl::unordered_dense::map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 74.19 ns lf=0.596
random_find_success 130.56 ns lf=0.596
find_failed 30.59 ns lf=0.596
find_empty 1.76 ns lf=0.000
insert 336.10 ns lf=0.596 321.7 MB (+39.0 MB)
insert_predicted 237.11 ns lf=0.596 321.7 MB (+39.0 MB)
insert_replace 170.13 ns lf=0.596 360.6 MB
emplace 419.15 ns lf=0.596 321.7 MB (+39.0 MB)
emplace_predicted 248.06 ns lf=0.596 321.7 MB (+39.0 MB)
emplace_replace 158.37 ns lf=0.596 360.6 MB
try_emplace 401.64 ns lf=0.596 321.7 MB (+39.0 MB)
try_emplace_predicted 244.17 ns lf=0.596 321.7 MB (+39.0 MB)
try_emplace_replace 291.25 ns lf=0.596 360.6 MB
operator [] 477.34 ns lf=0.596 321.7 MB (+39.0 MB)
operator [] predicted 323.91 ns lf=0.596 321.7 MB (+39.0 MB)
operator [] replace 146.78 ns lf=0.596 360.6 MB
serial_erase 175.89 ns lf=0.000 360.6 MB
random_erase 360.18 ns lf=0.000 360.6 MB
erase_failed 33.99 ns lf=0.596 360.6 MB
toggle 68.56 ns lf=0.000 321.7 MB
iterate 3.07 ns lf=0.596 360.6 MB
-----------------------------------------------------------------------------
改成向你那样的表格横向比较是不错,我一直想改成那样的,但一种没动手,思想上准备了的,我有在清理相关的接口。
提到 load_factor,我很早就想跟你说的,你很早也跟我提过,为了公平,要统一 load_factor 来测,我是有不同意见的。如果我没有写过 robin hood 哈希表,可能我会赞同你的意见,但是当我写了 robin hood 哈希表以后,才知道统一 load_factor 才是不公平的。我是因为想整理我几年前写的 jstd::Dictionay(拉链法+内置内存池+保存完整hash), jstd::Dictionay 在数据总量很小的时候,很快,因为所有 entry 分配一次以后,就不需要在移动,包括 rehash() 的时候,也不需要再做 hash,但是缺点嘛,也很明显,就是缓存使用量是很大的,高负载下,性能就不行了,我没看 aka::flat_hash_map 的视频之前是不知道这些事情的。
我是想搜索看看,还有没有更好的哈希表资料,无意中,我看到了 aka::flat_hash_map 作者在 CppNow2018 上的演讲视频,然后又找到了 Google 在 CppCon 2017 年的 Swiss Table 演讲视频,有点可惜的是,2019年前后,我写 jstd::Dictionay 没有搜索到这些视频,浪费了不少时间来写 jstd::Dictionay 。
aka::flat_hash_map 作者最伟大的发现,可能就是他发现 robin hood 哈希表 max_load_factor 设置在 0.5 左右,性能会更好,这看似简单的设置,还是非常有用的,针对 robin hood 哈希表 distance 不能太大(最大128),他还发明了 hash_policy,非常巧妙的解决了在同一个入口,distance 可能过大的问题。他还有不少神来之笔,我的 jstd::robin_hash_map 基本上是在他的基础上改进而来的。
这些不是重点,重点是 max_load_factor 设为 0.5,我试过,改大了,直接就卡死了,我现在是没启用 hash_policy,启用了之后,max_load_factor 可能可以设置得比 0.5 稍微大一点,但还是不能设置得太大,大了性能可能会打折扣。
我甚至在我自己写的 Swiss Table 版本 jstd::flat16_hash_map 也是设置为 mlf 0.5 的,所以这让我意识到,有的哈希表天生就不能支持太高的 load_factor,absl::flat_hash_map 其实在 load_factor 高了性能也会下降的,但 Google 可能太看重内存占用了, 给它加了一个 slot 整理,勉强支撑起较高的 mlf。所以每个哈希表都有其自己的特点,不必强求一定要统一,像拉链法,或者布谷鸟哈希表,或者类似链表法的哈希表,都可以做到很高的 load_factor,不瞒你说,我的 jstd::Directionay 设置的也是 1.0 的 mlf。
正是因为不同的哈希表方法,导致了不同的实现有它独特的特性,robin hood 和 Swiss Table 都很依赖空白位,空白位太少,或者连续的非空白位太多,性能就会下降很多,所以可能就是这些哈希表需要设置比较小的 mlf 的原因。
所以,不统一 load_factor 也没关系,load_factor 小,浪费的内存会多一点,但这也不代表最终的性能就会更慢,高负载下,决定性能的因素主要还是缓存局部性,污染的缓存行的总数量越小,性能可能就越好。
但是,从另一方面,也应该有另一个比赛项目,那就是在相同的 load_factor 的情况下,谁的性能最高,因为有的用户可能对内存的占用比较敏感,每种哈希表都有他的特点,优点和缺点,没有永远的银弹,这也是我想写文章来说说的原因。哈希表还有不少其他类型,有的我也还没有尝试过,不过在主流的实现中,目前也就基本这几个比较常见。
其实,我也看出来了,你的 emhash8 应该和 ankerl::unordered_dense::map 相似,用的索引,以及 entry 数据是保存在连续的数组里的,因为你们的内存使用量都比我 jstd::robin_hash_map 要小,而且你们内存使用量很接近。
早期emhash7 默认使用0.95 负载因子,插入性能影响很小(大多场景插入、rehash 比查询还是偏少),查询性能会慢20%左右,数据上看有性能损失但节省更多内存。unordered_dense 在高负载下性能比我的emhash8略差一些。 如果对性能极其敏感可以设置低一些,追求性能和内存平衡是一个不错的选择
测试场景喜欢使用大数据,实际场景并不多见,真正大map数据分布很少随机,把小hash map数据负载做高还是有好处。之前emhash7 元素低于64个满了才rehash,性能也不差,你可以考虑加一个大量小hashmap的测试
template<class hash_type>
void multi_small_ife(const std::string& hash_name, const std::vector<keyType>& vList)
{
#if KEY_INT
size_t sum = 0;
const auto hash_size = vList.size() / 1003 + 10;
const auto ts1 = getus();
if (test_case % 2) {
auto mh = new hash_type[hash_size];
for (const auto& v : vList) {
auto hash_id = ((uint32_t)v) % hash_size;
sum += mh[hash_id].emplace(v, TO_VAL(0)).second;
}
for (const auto& v : vList) {
auto hash_id = ((uint32_t)v) % hash_size;
sum += mh[hash_id].count(v);
}
for (const auto& v : vList) {
auto hash_id = ((uint32_t)v) % hash_size;
sum += mh[hash_id].erase(v + v % 2);
}
delete []mh;
} else {
hash_type hashm;
for (const auto v : vList) {
const keyType v2 = v % hash_size;
sum += hashm.emplace(v2, TO_VAL(0)).second;
sum += hashm.erase(v2 + v % 2);
sum += hashm.count(v2 / 2);
}
}
check_func_result(hash_name, __FUNCTION__, sum, ts1, 2);
#endif
}
5800H + clang11 , 结果有些差异
jstd::robin_hash_map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 113.65 ns lf=0.298
random_find_success 125.02 ns lf=0.298
find_failed 30.00 ns lf=0.298
find_empty 34.74 ns lf=0.000
insert 539.09 ns lf=0.298 298.5 MB (+128.0 MB)
insert_predicted 315.05 ns lf=0.298 298.5 MB (+128.0 MB)
insert_replace 100.72 ns lf=0.298 426.5 MB
emplace 547.90 ns lf=0.298 298.6 MB (+128.0 MB)
emplace_predicted 309.82 ns lf=0.298 298.6 MB (+128.0 MB)
emplace_replace 101.00 ns lf=0.298 426.6 MB
try_emplace 535.15 ns lf=0.298 298.6 MB (+128.0 MB)
try_emplace_predicted 326.18 ns lf=0.298 298.6 MB (+128.0 MB)
try_emplace_replace 117.61 ns lf=0.298 426.6 MB
operator [] 556.78 ns lf=0.298 298.6 MB (+128.0 MB)
operator [] predicted 325.17 ns lf=0.298 298.6 MB (+128.0 MB)
operator [] replace 139.05 ns lf=0.298 426.6 MB
serial_erase 180.30 ns lf=0.000 426.6 MB
random_erase 228.70 ns lf=0.000 426.6 MB
erase_failed 27.50 ns lf=0.298 426.6 MB
toggle 53.09 ns lf=0.000 298.6 MB
iterate 9.79 ns lf=0.298 426.6 MB (+0.0 MB)
emhash8::HashMap<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 81.93 ns lf=0.596
random_find_success 129.17 ns lf=0.596
find_failed 36.67 ns lf=0.596
find_empty 40.98 ns lf=0.000
insert 407.42 ns lf=0.596 319.4 MB (+38.2 MB)
insert_predicted 270.59 ns lf=0.596 319.4 MB (+38.1 MB)
insert_replace 117.56 ns lf=0.596 357.5 MB
emplace 409.24 ns lf=0.596 319.4 MB (+38.1 MB)
emplace_predicted 275.17 ns lf=0.596 319.4 MB (+38.1 MB)
emplace_replace 123.55 ns lf=0.596 357.5 MB
try_emplace 400.32 ns lf=0.596 319.4 MB (+38.1 MB)
try_emplace_predicted 272.28 ns lf=0.596 319.4 MB (+38.1 MB)
try_emplace_replace 177.69 ns lf=0.596 357.5 MB
operator [] 398.24 ns lf=0.596 319.4 MB (+38.1 MB)
operator [] predicted 282.72 ns lf=0.596 319.4 MB (+38.1 MB)
operator [] replace 133.60 ns lf=0.596 357.5 MB
serial_erase 159.30 ns lf=0.000 357.5 MB
random_erase 299.67 ns lf=0.000 357.5 MB
erase_failed 38.56 ns lf=0.596 357.5 MB
toggle 53.85 ns lf=0.000 319.4 MB
iterate 2.82 ns lf=0.596 357.5 MB
ankerl::unordered_dense::map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 77.12 ns lf=0.596
random_find_success 131.86 ns lf=0.596
find_failed 26.88 ns lf=0.596
find_empty 1.65 ns lf=0.000
insert 308.24 ns lf=0.596 319.4 MB (+38.1 MB)
insert_predicted 268.59 ns lf=0.596 319.4 MB (+38.1 MB)
insert_replace 124.81 ns lf=0.596 357.5 MB
emplace 313.85 ns lf=0.596 319.4 MB (+38.1 MB)
emplace_predicted 268.45 ns lf=0.596 319.4 MB (+38.1 MB)
emplace_replace 124.93 ns lf=0.596 357.5 MB
try_emplace 313.21 ns lf=0.596 319.4 MB (+38.1 MB)
try_emplace_predicted 264.79 ns lf=0.596 319.4 MB (+38.1 MB)
try_emplace_replace 276.97 ns lf=0.596 357.5 MB
operator [] 317.05 ns lf=0.596 319.4 MB (+38.1 MB)
operator [] predicted 271.61 ns lf=0.596 319.4 MB (+38.1 MB)
operator [] replace 84.33 ns lf=0.596 357.5 MB
serial_erase 167.31 ns lf=0.000 357.5 MB
random_erase 333.89 ns lf=0.000 357.5 MB
erase_failed 28.45 ns lf=0.596 357.5 MB
toggle 40.94 ns lf=0.000 319.4 MB
iterate 2.71 ns lf=0.596 357.5 MB
-----------------------------------------------------------------------------
刚才睡着了,看来还是有差距的,只不过 AMD EPYC 7K62 48核 的 L3 缓存比较大,没拉开差距。Intel 9700 可能是缓存的性能也不错,主频和内存频率也高,所以也没看出差距。
如果在arm,M1跑结果大不一样,看是很好的优化可能不一定有效果,手头目前没有机器,我的github主页有之前在华为鲲鹏 arm和m1跑的对比结果。
cpu + 缓存 + 编译器组合会产生不同测试结果。大多benchmark使用一组随机数据,而我的ebench采用500组随机case(load factor,hash, 随机生成器, key & value 多种组合, os,编译器)加权计算各种 hash map不同接口和整体综合 相对性能(对比使用绝对数值xxx us价值 不大 ) martin 的测试case很好,但他只在intel 8700测试得出结论不那么让人信服,至少我在服务器跑过结论有些不一样。
大数据 robin_hood::unordered_map(node_map) 比 robin_hood::unordered_flat_map 快。
if (1) {
measure_string_hashmap<robin_hood::unordered_map<Key, Value>>
("robin_hood::unordered_map<K, V>", obj_size, iters);
}
robin_hood::unordered_map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 87.23 ns lf=0.596
random_find_success 148.24 ns lf=0.596
find_failed 29.43 ns lf=0.596
find_empty 34.72 ns lf=0.000
insert 339.62 ns lf=0.596 319.4 MB
insert_predicted 206.10 ns lf=0.596 319.4 MB
insert_replace 161.67 ns lf=0.596 319.4 MB
emplace 340.25 ns lf=0.596 319.4 MB
emplace_predicted 202.33 ns lf=0.596 319.4 MB
emplace_replace 164.97 ns lf=0.596 319.4 MB
try_emplace 332.87 ns lf=0.596 319.4 MB
try_emplace_predicted 199.72 ns lf=0.596 319.4 MB
try_emplace_replace 338.48 ns lf=0.596 319.4 MB
operator [] 336.36 ns lf=0.596 319.4 MB (+0.0 MB)
operator [] predicted 206.55 ns lf=0.596 319.4 MB
operator [] replace 115.38 ns lf=0.596 319.4 MB
serial_erase 160.33 ns lf=0.000 319.4 MB
random_erase 294.65 ns lf=0.000 319.4 MB
erase_failed 29.45 ns lf=0.596 319.4 MB
toggle 60.02 ns lf=0.000 319.4 MB
iterate 10.55 ns lf=0.596 319.4 MB
ankerl::unordered_dense::map<K, V> (32 byte objects, 64 byte ValueType, 625000 iterations):
serial_find_success 87.65 ns lf=0.596
random_find_success 138.70 ns lf=0.596
find_failed 33.55 ns lf=0.596
find_empty 1.64 ns lf=0.000
insert 320.50 ns lf=0.596 319.4 MB (+38.1 MB)
insert_predicted 265.44 ns lf=0.596 319.4 MB (+38.1 MB)
insert_replace 131.12 ns lf=0.596 357.6 MB
emplace 306.81 ns lf=0.596 319.4 MB (+38.1 MB)
emplace_predicted 266.15 ns lf=0.596 319.4 MB (+38.1 MB)
emplace_replace 149.73 ns lf=0.596 357.6 MB
try_emplace 314.28 ns lf=0.596 319.4 MB (+38.1 MB)
try_emplace_predicted 278.55 ns lf=0.596 319.4 MB (+38.1 MB)
try_emplace_replace 291.62 ns lf=0.596 357.6 MB
operator [] 307.71 ns lf=0.596 319.4 MB (+38.1 MB)
operator [] predicted 278.75 ns lf=0.596 319.4 MB (+38.1 MB)
operator [] replace 86.23 ns lf=0.596 357.6 MB
serial_erase 160.52 ns lf=0.000 357.6 MB
random_erase 324.57 ns lf=0.000 357.6 MB
erase_failed 29.51 ns lf=0.596 357.6 MB
toggle 39.32 ns lf=0.000 319.4 MB
iterate 2.60 ns lf=0.596 357.6 MB
-----------------------------------------------------------------------------
我测试了一下,USE_JSTD_ROBIN_HASH_MAP 倒没发现编译错误,但由于早上我改了一下 aka::flat_hash_map,倒是有个编译错误,已经修正。
更新 submodule 建议使用:
# 更新到仓库指定的版本,而不是最新版本
git submodule update --init --recursive
不要使用 git submodule update --remote --recursive
,因为这句会更新到 submodule 的最新版本,可能由于还在开发中或者版本不兼容导致编译错误,或者运行错误。
jstd_hashmap 我就懒得切换到 develop 分支里修改了,直接就是在 master 分支里修改的,所以最新版不一定能编译通过,或者保证运行结果是正确的。我测试好了,再更新到 hashmap_benchmark 里,所以使用上面的第一条命令更新 submodule 即可。
另外,我改成 robin_hood::unordered_map 了,这是一个自适应版本,会自动切换 flat_map 和 node_map 。
https://github.com/ktprime/emhash 其中 emhash7 整数类型k.v速度比较快,负载因子load_factor 可以设置接近1.0 emhash8 是dense_hash_map 实现,迭代,非整数类型k.v 以及查找命中不错性能 , 删除比较慢