-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[libcxx] Avoid hash key in __hash_table::find() if it is empty. #126837
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
If the hash table has no buckets yet, it's empty and the find will do fast return end(). Then compute hash key is useless and can be avoided, since it could be expensive for some key types, such as long string. This is a small optimization but useful in cases like a checklist ( implemented as unordered_set) that is mostly empty.
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-libcxx Author: None (xbcnn) ChangesIf the hash table has no buckets yet, it's empty and the find will do fast return end(). Then compute hash key is useless and can be avoided, since it could be expensive for some key types, such as long string. This is a small optimization but useful in cases like a checklist ( implemented as unordered_set) that is mostly empty. Full diff: https://github.com/llvm/llvm-project/pull/126837.diff 1 Files Affected:
diff --git a/libcxx/include/__hash_table b/libcxx/include/__hash_table
index d7b312f8774fc..a1d06d07f7c8d 100644
--- a/libcxx/include/__hash_table
+++ b/libcxx/include/__hash_table
@@ -1771,9 +1771,9 @@ template <class _Tp, class _Hash, class _Equal, class _Alloc>
template <class _Key>
typename __hash_table<_Tp, _Hash, _Equal, _Alloc>::iterator
__hash_table<_Tp, _Hash, _Equal, _Alloc>::find(const _Key& __k) {
- size_t __hash = hash_function()(__k);
size_type __bc = bucket_count();
if (__bc != 0) {
+ size_t __hash = hash_function()(__k);
size_t __chash = std::__constrain_hash(__hash, __bc);
__next_pointer __nd = __bucket_list_[__chash];
if (__nd != nullptr) {
@@ -1792,9 +1792,9 @@ template <class _Tp, class _Hash, class _Equal, class _Alloc>
template <class _Key>
typename __hash_table<_Tp, _Hash, _Equal, _Alloc>::const_iterator
__hash_table<_Tp, _Hash, _Equal, _Alloc>::find(const _Key& __k) const {
- size_t __hash = hash_function()(__k);
size_type __bc = bucket_count();
if (__bc != 0) {
+ size_t __hash = hash_function()(__k);
size_t __chash = std::__constrain_hash(__hash, __bc);
__next_pointer __nd = __bucket_list_[__chash];
if (__nd != nullptr) {
|
This is my first PR to the LLVM project. Please help to review and comments if any problem. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you provide some benchmarks for this change?
First thanks for reviews.
Sure, but I probably need some guides such as where to put the benchmark src and how to build/run it. |
Benchmarks are in |
I add a separate benchmark: I pre-generate 32K random strings(each 32-128 characters long), and do find iteration ranging from 1024~32768. With the opt:
Without the opt:
On empty set, it's about 10+ times gains since no hash key computed. |
I think we could update |
✅ With the latest revision this PR passed the C/C++ code formatter. |
We could go ahead to double check |
libcxx/test/benchmarks/containers/associative/hash_table_find.bench.cpp
Outdated
Show resolved
Hide resolved
Updated benchmarks: Opt with size() check
No opt
|
Hi @philnik777 @ldionne @frederick-vs-ja I've added |
Hi @philnik777 @frederick-vs-ja |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is fine. @philnik777
@@ -0,0 +1,97 @@ | |||
//===----------------------------------------------------------------------===// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please update associative_container_benchmarks.h
to include zero-sized benchmarks (where appropriate) instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. I added with_expensive_key_empty
to cover the tests and removed hash_table_find.bench.cpp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary to add ->Arg(0)
to bench
in associative_container_benchmarks.h
?
auto bench = [&](std::string operation, auto f) {
benchmark::RegisterBenchmark(container + "::" + operation, f)->Arg(0)->Arg(32)->Arg(1024)->Arg(8192);
};
For expensive key, such as std::unordered_set<std::string>, the benchmark reulst: 1. With the opt: ------------------------------------------------------------------------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------------------------------------------------------------------------- std::unordered_set<int>::ctor(const&)/32 4717 ns 4718 ns 143456 std::unordered_set<int>::ctor(const&)/1024 160766 ns 160764 ns 4384 std::unordered_set<int>::ctor(const&)/8192 1362608 ns 1362523 ns 544 std::unordered_set<int>::ctor(iterator, iterator) (unsorted sequence)/32 6493 ns 6493 ns 104416 std::unordered_set<int>::ctor(iterator, iterator) (unsorted sequence)/1024 204541 ns 204539 ns 3456 std::unordered_set<int>::ctor(iterator, iterator) (unsorted sequence)/8192 1712671 ns 1712620 ns 416 std::unordered_set<int>::ctor(iterator, iterator) (sorted sequence)/32 6533 ns 6534 ns 107136 std::unordered_set<int>::ctor(iterator, iterator) (sorted sequence)/1024 202608 ns 202603 ns 3456 std::unordered_set<int>::ctor(iterator, iterator) (sorted sequence)/8192 1707584 ns 1707476 ns 416 std::unordered_set<int>::operator=(const&)/32 4814 ns 4814 ns 147424 std::unordered_set<int>::operator=(const&)/1024 154541 ns 154535 ns 4416 std::unordered_set<int>::operator=(const&)/8192 1310847 ns 1310769 ns 416 std::unordered_set<int>::insert(value) (already present)/32 57.5 ns 57.5 ns 12193408 std::unordered_set<int>::insert(value) (already present)/1024 60.4 ns 60.4 ns 11587808 std::unordered_set<int>::insert(value) (already present)/8192 60.7 ns 60.7 ns 9435360 std::unordered_set<int>::insert(value) (new value)/32 153 ns 154 ns 4433696 std::unordered_set<int>::insert(value) (new value)/1024 164 ns 164 ns 3980992 std::unordered_set<int>::insert(value) (new value)/8192 165 ns 165 ns 4045664 std::unordered_set<int>::insert(iterator, iterator) (all new keys)/32 6416 ns 6419 ns 102879 std::unordered_set<int>::insert(iterator, iterator) (all new keys)/1024 205733 ns 205748 ns 3425 std::unordered_set<int>::insert(iterator, iterator) (all new keys)/8192 1583461 ns 1583454 ns 443 std::unordered_set<int>::insert(iterator, iterator) (half new keys)/32 4363 ns 4370 ns 154592 std::unordered_set<int>::insert(iterator, iterator) (half new keys)/1024 141932 ns 141921 ns 5014 std::unordered_set<int>::insert(iterator, iterator) (half new keys)/8192 1096349 ns 1096330 ns 641 std::unordered_set<int>::erase(key) (existent)/32 149 ns 149 ns 4715776 std::unordered_set<int>::erase(key) (existent)/1024 153 ns 153 ns 4587936 std::unordered_set<int>::erase(key) (existent)/8192 157 ns 157 ns 3200000 std::unordered_set<int>::erase(key) (non-existent)/32 48.6 ns 48.6 ns 14433088 std::unordered_set<int>::erase(key) (non-existent)/1024 55.4 ns 55.4 ns 14368768 std::unordered_set<int>::erase(key) (non-existent)/8192 50.1 ns 50.0 ns 12491488 std::unordered_set<int>::erase(iterator)/32 106 ns 106 ns 6649824 std::unordered_set<int>::erase(iterator)/1024 108 ns 107 ns 6551744 std::unordered_set<int>::erase(iterator)/8192 108 ns 108 ns 6368928 std::unordered_set<int>::erase(iterator, iterator) (erase half the container)/32 1869 ns 1861 ns 373187 std::unordered_set<int>::erase(iterator, iterator) (erase half the container)/1024 50300 ns 50256 ns 13894 std::unordered_set<int>::erase(iterator, iterator) (erase half the container)/8192 410477 ns 410379 ns 1707 std::unordered_set<int>::clear()/32 1047 ns 1038 ns 672424 std::unordered_set<int>::clear()/1024 24126 ns 24090 ns 29056 std::unordered_set<int>::clear()/8192 188620 ns 188534 ns 3693 std::unordered_set<int>::find(key) (existent)/32 52.2 ns 52.2 ns 13406688 std::unordered_set<int>::find(key) (existent)/1024 48.4 ns 48.4 ns 13773728 std::unordered_set<int>::find(key) (existent)/8192 55.8 ns 55.8 ns 12571776 std::unordered_set<int>::find(key) (non-existent)/32 41.0 ns 41.0 ns 14667808 std::unordered_set<int>::find(key) (non-existent)/1024 43.5 ns 43.5 ns 16278688 std::unordered_set<int>::find(key) (non-existent)/8192 41.2 ns 41.2 ns 15790752 std::unordered_set<int>::find(key) (expensive-empty)/32 14.6 ns 14.6 ns 47838272 std::unordered_set<int>::find(key) (expensive-empty)/1024 14.6 ns 14.6 ns 47741568 std::unordered_set<int>::find(key) (expensive-empty)/8192 14.7 ns 14.7 ns 47780224 std::unordered_set<int>::count(key) (existent)/32 58.4 ns 58.4 ns 12129728 std::unordered_set<int>::count(key) (existent)/1024 57.4 ns 57.4 ns 12091968 std::unordered_set<int>::count(key) (existent)/8192 61.1 ns 61.1 ns 12053312 std::unordered_set<int>::count(key) (non-existent)/32 60.7 ns 60.7 ns 13021152 std::unordered_set<int>::count(key) (non-existent)/1024 57.1 ns 57.1 ns 12352160 std::unordered_set<int>::count(key) (non-existent)/8192 61.1 ns 61.1 ns 14251424 std::unordered_set<int>::count(key) (expensive-empty)/32 19.4 ns 19.4 ns 36120640 std::unordered_set<int>::count(key) (expensive-empty)/1024 19.4 ns 19.4 ns 36122336 std::unordered_set<int>::count(key) (expensive-empty)/8192 19.4 ns 19.4 ns 35992544 std::unordered_set<int>::contains(key) (existent)/32 57.7 ns 57.7 ns 11540576 std::unordered_set<int>::contains(key) (existent)/1024 56.1 ns 56.1 ns 11500288 std::unordered_set<int>::contains(key) (existent)/8192 60.5 ns 60.5 ns 11040832 std::unordered_set<int>::contains(key) (non-existent)/32 58.9 ns 58.9 ns 13091488 std::unordered_set<int>::contains(key) (non-existent)/1024 53.0 ns 53.0 ns 12597184 std::unordered_set<int>::contains(key) (non-existent)/8192 56.1 ns 56.1 ns 12456960 std::unordered_set<int>::contains(key) (expensive-empty)/32 21.4 ns 21.4 ns 32681408 std::unordered_set<int>::contains(key) (expensive-empty)/1024 21.4 ns 21.4 ns 32516640 std::unordered_set<int>::contains(key) (expensive-empty)/8192 21.4 ns 21.4 ns 32573888 std::unordered_set<std::string>::ctor(const&)/32 24879 ns 24880 ns 25856 std::unordered_set<std::string>::ctor(const&)/1024 884089 ns 884042 ns 832 std::unordered_set<std::string>::ctor(const&)/8192 8585689 ns 8585383 ns 96 std::unordered_set<std::string>::ctor(iterator, iterator) (unsorted sequence)/32 29851 ns 29853 ns 22944 std::unordered_set<std::string>::ctor(iterator, iterator) (unsorted sequence)/1024 1149105 ns 1149052 ns 768 std::unordered_set<std::string>::ctor(iterator, iterator) (unsorted sequence)/8192 9769845 ns 9769243 ns 64 std::unordered_set<std::string>::ctor(iterator, iterator) (sorted sequence)/32 33674 ns 33678 ns 26624 std::unordered_set<std::string>::ctor(iterator, iterator) (sorted sequence)/1024 1058258 ns 1058235 ns 544 std::unordered_set<std::string>::ctor(iterator, iterator) (sorted sequence)/8192 9624318 ns 9624047 ns 96 std::unordered_set<std::string>::operator=(const&)/32 48214 ns 48216 ns 15296 std::unordered_set<std::string>::operator=(const&)/1024 1523473 ns 1523422 ns 448 std::unordered_set<std::string>::operator=(const&)/8192 13500351 ns 13499850 ns 64 std::unordered_set<std::string>::insert(value) (already present)/32 324 ns 324 ns 2154816 std::unordered_set<std::string>::insert(value) (already present)/1024 731 ns 731 ns 964224 std::unordered_set<std::string>::insert(value) (already present)/8192 1463 ns 1463 ns 460064 std::unordered_set<std::string>::insert(value) (new value)/32 1005 ns 1004 ns 1843488 std::unordered_set<std::string>::insert(value) (new value)/1024 1299 ns 1191 ns 636960 std::unordered_set<std::string>::insert(value) (new value)/8192 1726 ns 1702 ns 320000 std::unordered_set<std::string>::insert(iterator, iterator) (all new keys)/32 44153 ns 42927 ns 17413 std::unordered_set<std::string>::insert(iterator, iterator) (all new keys)/1024 1476626 ns 1447248 ns 482 std::unordered_set<std::string>::insert(iterator, iterator) (all new keys)/8192 13019197 ns 12896650 ns 55 std::unordered_set<std::string>::insert(iterator, iterator) (half new keys)/32 40150 ns 40125 ns 16551 std::unordered_set<std::string>::insert(iterator, iterator) (half new keys)/1024 1407984 ns 1341443 ns 497 std::unordered_set<std::string>::insert(iterator, iterator) (half new keys)/8192 12215376 ns 12203965 ns 57 std::unordered_set<std::string>::erase(key) (existent)/32 2569 ns 1997 ns 349312 std::unordered_set<std::string>::erase(key) (existent)/1024 1970 ns 1927 ns 930304 std::unordered_set<std::string>::erase(key) (existent)/8192 1621 ns 1618 ns 529216 std::unordered_set<std::string>::erase(key) (non-existent)/32 1174 ns 986 ns 623360 std::unordered_set<std::string>::erase(key) (non-existent)/1024 1194 ns 1048 ns 676672 std::unordered_set<std::string>::erase(key) (non-existent)/8192 983 ns 962 ns 688000 std::unordered_set<std::string>::erase(iterator)/32 213 ns 209 ns 3374560 std::unordered_set<std::string>::erase(iterator)/1024 212 ns 207 ns 3474080 std::unordered_set<std::string>::erase(iterator)/8192 218 ns 206 ns 3805824 std::unordered_set<std::string>::erase(iterator, iterator) (erase half the container)/32 2854 ns 2804 ns 208147 std::unordered_set<std::string>::erase(iterator, iterator) (erase half the container)/1024 64984 ns 64778 ns 10493 std::unordered_set<std::string>::erase(iterator, iterator) (erase half the container)/8192 695737 ns 695149 ns 1013 std::unordered_set<std::string>::clear()/32 1388 ns 1370 ns 506834 std::unordered_set<std::string>::clear()/1024 46217 ns 46073 ns 15204 std::unordered_set<std::string>::clear()/8192 599831 ns 599498 ns 1180 std::unordered_set<std::string>::find(key) (existent)/32 753 ns 753 ns 1039520 std::unordered_set<std::string>::find(key) (existent)/1024 662 ns 662 ns 968320 std::unordered_set<std::string>::find(key) (existent)/8192 610 ns 610 ns 1020864 std::unordered_set<std::string>::find(key) (non-existent)/32 625 ns 625 ns 1117408 std::unordered_set<std::string>::find(key) (non-existent)/1024 655 ns 655 ns 1083008 std::unordered_set<std::string>::find(key) (non-existent)/8192 650 ns 650 ns 1042560 std::unordered_set<std::string>::find(key) (expensive-empty)/32 14.4 ns 14.4 ns 48641472 std::unordered_set<std::string>::find(key) (expensive-empty)/1024 14.4 ns 14.4 ns 48623392 std::unordered_set<std::string>::find(key) (expensive-empty)/8192 14.4 ns 14.4 ns 48591616 std::unordered_set<std::string>::count(key) (existent)/32 824 ns 824 ns 985760 std::unordered_set<std::string>::count(key) (existent)/1024 776 ns 776 ns 929376 std::unordered_set<std::string>::count(key) (existent)/8192 703 ns 702 ns 995936 std::unordered_set<std::string>::count(key) (non-existent)/32 725 ns 725 ns 1087584 std::unordered_set<std::string>::count(key) (non-existent)/1024 709 ns 709 ns 937664 std::unordered_set<std::string>::count(key) (non-existent)/8192 625 ns 625 ns 1144000 std::unordered_set<std::string>::count(key) (expensive-empty)/32 19.3 ns 19.3 ns 36164832 std::unordered_set<std::string>::count(key) (expensive-empty)/1024 19.3 ns 19.3 ns 36393312 std::unordered_set<std::string>::count(key) (expensive-empty)/8192 19.3 ns 19.3 ns 36143328 std::unordered_set<std::string>::contains(key) (existent)/32 709 ns 709 ns 1048064 std::unordered_set<std::string>::contains(key) (existent)/1024 748 ns 748 ns 1024768 std::unordered_set<std::string>::contains(key) (existent)/8192 831 ns 831 ns 895008 std::unordered_set<std::string>::contains(key) (non-existent)/32 714 ns 714 ns 1075264 std::unordered_set<std::string>::contains(key) (non-existent)/1024 504 ns 504 ns 1222240 std::unordered_set<std::string>::contains(key) (non-existent)/8192 717 ns 717 ns 1022432 std::unordered_set<std::string>::contains(key) (expensive-empty)/32 22.0 ns 22.0 ns 31752096 std::unordered_set<std::string>::contains(key) (expensive-empty)/1024 22.0 ns 22.0 ns 31857600 std::unordered_set<std::string>::contains(key) (expensive-empty)/8192 22.0 ns 22.0 ns 31783040 2. Without the opt: ------------------------------------------------------------------------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------------------------------------------------------------------------- std::unordered_set<int>::ctor(const&)/32 4920 ns 4921 ns 142304 std::unordered_set<int>::ctor(const&)/1024 161004 ns 161004 ns 4384 std::unordered_set<int>::ctor(const&)/8192 1369984 ns 1369913 ns 512 std::unordered_set<int>::ctor(iterator, iterator) (unsorted sequence)/32 6583 ns 6584 ns 105536 std::unordered_set<int>::ctor(iterator, iterator) (unsorted sequence)/1024 203335 ns 203328 ns 3488 std::unordered_set<int>::ctor(iterator, iterator) (unsorted sequence)/8192 1712738 ns 1712659 ns 416 std::unordered_set<int>::ctor(iterator, iterator) (sorted sequence)/32 6417 ns 6418 ns 105920 std::unordered_set<int>::ctor(iterator, iterator) (sorted sequence)/1024 204252 ns 204248 ns 3456 std::unordered_set<int>::ctor(iterator, iterator) (sorted sequence)/8192 1718282 ns 1718226 ns 416 std::unordered_set<int>::operator=(const&)/32 4777 ns 4778 ns 143776 std::unordered_set<int>::operator=(const&)/1024 155375 ns 155370 ns 4416 std::unordered_set<int>::operator=(const&)/8192 1318345 ns 1318318 ns 416 std::unordered_set<int>::insert(value) (already present)/32 57.1 ns 57.1 ns 12182624 std::unordered_set<int>::insert(value) (already present)/1024 60.2 ns 60.2 ns 11640704 std::unordered_set<int>::insert(value) (already present)/8192 59.3 ns 59.3 ns 11515456 std::unordered_set<int>::insert(value) (new value)/32 152 ns 152 ns 4575936 std::unordered_set<int>::insert(value) (new value)/1024 161 ns 161 ns 4283488 std::unordered_set<int>::insert(value) (new value)/8192 172 ns 172 ns 4288736 std::unordered_set<int>::insert(iterator, iterator) (all new keys)/32 6430 ns 6434 ns 107732 std::unordered_set<int>::insert(iterator, iterator) (all new keys)/1024 206167 ns 206184 ns 3429 std::unordered_set<int>::insert(iterator, iterator) (all new keys)/8192 1587621 ns 1587634 ns 417 std::unordered_set<int>::insert(iterator, iterator) (half new keys)/32 4588 ns 4587 ns 150781 std::unordered_set<int>::insert(iterator, iterator) (half new keys)/1024 132835 ns 132832 ns 4951 std::unordered_set<int>::insert(iterator, iterator) (half new keys)/8192 1095236 ns 1095210 ns 641 std::unordered_set<int>::erase(key) (existent)/32 154 ns 154 ns 4492416 std::unordered_set<int>::erase(key) (existent)/1024 160 ns 160 ns 4383520 std::unordered_set<int>::erase(key) (existent)/8192 155 ns 155 ns 4363744 std::unordered_set<int>::erase(key) (non-existent)/32 57.8 ns 57.8 ns 14141664 std::unordered_set<int>::erase(key) (non-existent)/1024 57.4 ns 57.4 ns 14369216 std::unordered_set<int>::erase(key) (non-existent)/8192 56.4 ns 56.4 ns 13152576 std::unordered_set<int>::erase(iterator)/32 108 ns 108 ns 6519872 std::unordered_set<int>::erase(iterator)/1024 110 ns 110 ns 6271040 std::unordered_set<int>::erase(iterator)/8192 112 ns 112 ns 6295872 std::unordered_set<int>::erase(iterator, iterator) (erase half the container)/32 1815 ns 1808 ns 363644 std::unordered_set<int>::erase(iterator, iterator) (erase half the container)/1024 51034 ns 51007 ns 13752 std::unordered_set<int>::erase(iterator, iterator) (erase half the container)/8192 414144 ns 414067 ns 1692 std::unordered_set<int>::clear()/32 1053 ns 1049 ns 660370 std::unordered_set<int>::clear()/1024 24122 ns 24110 ns 29062 std::unordered_set<int>::clear()/8192 189528 ns 189457 ns 3704 std::unordered_set<int>::find(key) (existent)/32 52.0 ns 52.0 ns 12869664 std::unordered_set<int>::find(key) (existent)/1024 51.0 ns 51.0 ns 13863392 std::unordered_set<int>::find(key) (existent)/8192 52.8 ns 52.8 ns 13966656 std::unordered_set<int>::find(key) (non-existent)/32 43.0 ns 43.0 ns 13238752 std::unordered_set<int>::find(key) (non-existent)/1024 46.9 ns 46.9 ns 17560800 std::unordered_set<int>::find(key) (non-existent)/8192 40.5 ns 40.5 ns 17009920 std::unordered_set<int>::find(key) (expensive-empty)/32 16.4 ns 16.4 ns 42983616 std::unordered_set<int>::find(key) (expensive-empty)/1024 16.5 ns 16.5 ns 42652352 std::unordered_set<int>::find(key) (expensive-empty)/8192 16.3 ns 16.3 ns 42610592 std::unordered_set<int>::count(key) (existent)/32 58.6 ns 58.6 ns 11978688 std::unordered_set<int>::count(key) (existent)/1024 56.3 ns 56.3 ns 12275488 std::unordered_set<int>::count(key) (existent)/8192 61.3 ns 61.3 ns 11718880 std::unordered_set<int>::count(key) (non-existent)/32 51.6 ns 51.6 ns 13867456 std::unordered_set<int>::count(key) (non-existent)/1024 59.7 ns 59.7 ns 12946400 std::unordered_set<int>::count(key) (non-existent)/8192 57.9 ns 57.9 ns 12481344 std::unordered_set<int>::count(key) (expensive-empty)/32 21.5 ns 21.5 ns 32519840 std::unordered_set<int>::count(key) (expensive-empty)/1024 21.5 ns 21.5 ns 32430592 std::unordered_set<int>::count(key) (expensive-empty)/8192 21.5 ns 21.5 ns 32623872 std::unordered_set<int>::contains(key) (existent)/32 58.3 ns 58.3 ns 11878944 std::unordered_set<int>::contains(key) (existent)/1024 61.1 ns 61.1 ns 11275648 std::unordered_set<int>::contains(key) (existent)/8192 59.8 ns 59.8 ns 11298112 std::unordered_set<int>::contains(key) (non-existent)/32 52.4 ns 52.4 ns 11874720 std::unordered_set<int>::contains(key) (non-existent)/1024 56.1 ns 56.1 ns 11843904 std::unordered_set<int>::contains(key) (non-existent)/8192 57.3 ns 57.3 ns 10858048 std::unordered_set<int>::contains(key) (expensive-empty)/32 23.1 ns 23.1 ns 30402240 std::unordered_set<int>::contains(key) (expensive-empty)/1024 23.1 ns 23.1 ns 30244128 std::unordered_set<int>::contains(key) (expensive-empty)/8192 23.1 ns 23.1 ns 30230528 std::unordered_set<std::string>::ctor(const&)/32 26497 ns 26497 ns 25280 std::unordered_set<std::string>::ctor(const&)/1024 825878 ns 825839 ns 832 std::unordered_set<std::string>::ctor(const&)/8192 8670858 ns 8670549 ns 96 std::unordered_set<std::string>::ctor(iterator, iterator) (unsorted sequence)/32 27310 ns 27311 ns 26464 std::unordered_set<std::string>::ctor(iterator, iterator) (unsorted sequence)/1024 946828 ns 946803 ns 768 std::unordered_set<std::string>::ctor(iterator, iterator) (unsorted sequence)/8192 9053635 ns 9053223 ns 96 std::unordered_set<std::string>::ctor(iterator, iterator) (sorted sequence)/32 27548 ns 27548 ns 23648 std::unordered_set<std::string>::ctor(iterator, iterator) (sorted sequence)/1024 898692 ns 898659 ns 800 std::unordered_set<std::string>::ctor(iterator, iterator) (sorted sequence)/8192 9010872 ns 9010567 ns 96 std::unordered_set<std::string>::operator=(const&)/32 47648 ns 47650 ns 14080 std::unordered_set<std::string>::operator=(const&)/1024 1475556 ns 1475497 ns 448 std::unordered_set<std::string>::operator=(const&)/8192 13089133 ns 13088427 ns 64 std::unordered_set<std::string>::insert(value) (already present)/32 946 ns 946 ns 796928 std::unordered_set<std::string>::insert(value) (already present)/1024 729 ns 729 ns 2565824 std::unordered_set<std::string>::insert(value) (already present)/8192 805 ns 805 ns 2718624 std::unordered_set<std::string>::insert(value) (new value)/32 1161 ns 1161 ns 1403360 std::unordered_set<std::string>::insert(value) (new value)/1024 1246 ns 1246 ns 1978080 std::unordered_set<std::string>::insert(value) (new value)/8192 1110 ns 1110 ns 985376 std::unordered_set<std::string>::insert(iterator, iterator) (all new keys)/32 30256 ns 30276 ns 25741 std::unordered_set<std::string>::insert(iterator, iterator) (all new keys)/1024 908942 ns 909062 ns 745 std::unordered_set<std::string>::insert(iterator, iterator) (all new keys)/8192 7286785 ns 7286437 ns 96 std::unordered_set<std::string>::insert(iterator, iterator) (half new keys)/32 26705 ns 26699 ns 26325 std::unordered_set<std::string>::insert(iterator, iterator) (half new keys)/1024 837638 ns 837685 ns 835 std::unordered_set<std::string>::insert(iterator, iterator) (half new keys)/8192 6982924 ns 6982639 ns 100 std::unordered_set<std::string>::erase(key) (existent)/32 1082 ns 1082 ns 1315040 std::unordered_set<std::string>::erase(key) (existent)/1024 815 ns 815 ns 1036096 std::unordered_set<std::string>::erase(key) (existent)/8192 1004 ns 1004 ns 868960 std::unordered_set<std::string>::erase(key) (non-existent)/32 632 ns 632 ns 1007424 std::unordered_set<std::string>::erase(key) (non-existent)/1024 609 ns 609 ns 1200256 std::unordered_set<std::string>::erase(key) (non-existent)/8192 749 ns 749 ns 1040416 std::unordered_set<std::string>::erase(iterator)/32 132 ns 131 ns 5600064 std::unordered_set<std::string>::erase(iterator)/1024 125 ns 125 ns 5720800 std::unordered_set<std::string>::erase(iterator)/8192 130 ns 130 ns 5698336 std::unordered_set<std::string>::erase(iterator, iterator) (erase half the container)/32 2188 ns 2166 ns 329336 std::unordered_set<std::string>::erase(iterator, iterator) (erase half the container)/1024 66079 ns 65889 ns 10574 std::unordered_set<std::string>::erase(iterator, iterator) (erase half the container)/8192 685952 ns 685607 ns 1001 std::unordered_set<std::string>::clear()/32 1388 ns 1372 ns 507157 std::unordered_set<std::string>::clear()/1024 47859 ns 47698 ns 15356 std::unordered_set<std::string>::clear()/8192 573679 ns 573370 ns 1137 std::unordered_set<std::string>::find(key) (existent)/32 728 ns 728 ns 955840 std::unordered_set<std::string>::find(key) (existent)/1024 682 ns 682 ns 926912 std::unordered_set<std::string>::find(key) (existent)/8192 648 ns 648 ns 923872 std::unordered_set<std::string>::find(key) (non-existent)/32 646 ns 646 ns 958112 std::unordered_set<std::string>::find(key) (non-existent)/1024 702 ns 702 ns 1090208 std::unordered_set<std::string>::find(key) (non-existent)/8192 594 ns 594 ns 1150432 std::unordered_set<std::string>::find(key) (expensive-empty)/32 313 ns 313 ns 2236000 std::unordered_set<std::string>::find(key) (expensive-empty)/1024 313 ns 313 ns 2237376 std::unordered_set<std::string>::find(key) (expensive-empty)/8192 313 ns 313 ns 2233696 std::unordered_set<std::string>::count(key) (existent)/32 803 ns 803 ns 987712 std::unordered_set<std::string>::count(key) (existent)/1024 670 ns 670 ns 919680 std::unordered_set<std::string>::count(key) (existent)/8192 668 ns 668 ns 878944 std::unordered_set<std::string>::count(key) (non-existent)/32 706 ns 706 ns 966848 std::unordered_set<std::string>::count(key) (non-existent)/1024 558 ns 558 ns 979904 std::unordered_set<std::string>::count(key) (non-existent)/8192 632 ns 632 ns 1027840 std::unordered_set<std::string>::count(key) (expensive-empty)/32 319 ns 319 ns 2196512 std::unordered_set<std::string>::count(key) (expensive-empty)/1024 318 ns 318 ns 2199328 std::unordered_set<std::string>::count(key) (expensive-empty)/8192 318 ns 318 ns 2197472 std::unordered_set<std::string>::contains(key) (existent)/32 708 ns 708 ns 1037120 std::unordered_set<std::string>::contains(key) (existent)/1024 765 ns 765 ns 889856 std::unordered_set<std::string>::contains(key) (existent)/8192 740 ns 740 ns 945600 std::unordered_set<std::string>::contains(key) (non-existent)/32 730 ns 730 ns 1077984 std::unordered_set<std::string>::contains(key) (non-existent)/1024 662 ns 662 ns 1001536 std::unordered_set<std::string>::contains(key) (non-existent)/8192 662 ns 662 ns 1024864 std::unordered_set<std::string>::contains(key) (expensive-empty)/32 321 ns 321 ns 2182656 std::unordered_set<std::string>::contains(key) (expensive-empty)/1024 320 ns 320 ns 2188096 std::unordered_set<std::string>::contains(key) (expensive-empty)/8192 320 ns 320 ns 2188192
Sorry for delayed response as I was on vacation for the past days. |
If the hash table is empty, with or without buckets, the find() can do fast return. Then computing hash key is useless and avoidable, since it could be expensive for some key types, such as long strings.
This is a small optimization but useful in cases like a checklist (unordered_set/map) which is mostly empty.
For expensive hash key types, i.e., long
std::string
, there is about 10x times faster.For non-expensive
int
key, they are close, still a little faster.