Skip to content

Add ASKL Map#4

Open
RaphaelPrevost wants to merge 1 commit into
JacksonAllan:mainfrom
RaphaelPrevost:askl_map
Open

Add ASKL Map#4
RaphaelPrevost wants to merge 1 commit into
JacksonAllan:mainfrom
RaphaelPrevost:askl_map

Conversation

@RaphaelPrevost

Copy link
Copy Markdown

Hi Jackson,

Thanks again for the feedback on Reddit!

This PR adds an askl_map shim with the following quirks:

  • ASKL stores keys internally, so I had to make a small adapter for the iterator API
  • I kept rapidhashnano rather than FNV, because cuckoo hashing is very sensitive to hash distribution
  • I had to make a few changes to the ASKL original code so it compiles as C++

I think the MinGW compile issues you pointed out should be fixed, but if you have a chance to try it on your setup I'd be grateful if you could confirm it.

Happy to improve the shim if you see some correctness/fairness issues.

@JacksonAllan

Copy link
Copy Markdown
Owner

Thanks for doing this work! I'm working on getting it to compile now. Then I can run the benchmarks and share the results with you.

Currently, I get quite a few errors and warnings that fall into the following categories:

  • Warnings related to C++'s lack of support for flexible array members. I can probably ignore these for the purposes of this benchmark, but you would need to remove the flexible array member and rely on pointer arithmetic instead if you wanted askl_map to support C++ (probably not necessary as C++ users would prefer a template-based library anyway).
  • Errors about ASKL's gettimeofday function conflicting with Abseil's. This probably has more to do with the fact that I'm embedding the relevant parts of Abseil directly into the project rather than compiling Abseil separately as it was designed. However, if you intend askl_map to be used separately from ASKL, then you might want to consider removing the dependency on system headers (are they really needed for a hash table implementation?).
  • Errors related to converting void * to other pointer types without an explicit cast (legal in C but illegal in C++).

Verstable is also causing an error due to a conflict between a macro it defines (VT_EMPTY) and an identically named enum constant in a Windows header (as ASKL apparently includes windows.h and is being compiled after Verstable, at least per the current shim order). Verstable probably should undef its internal macros at the end of its header.

I'll modify ASKL to fix each of these issues - hopefully it will compile then.

As for the hash function, these benchmarks try to level the playing field by forcing each library to use the same hash function (FNV-1a in the case of strings) and the same maximum load factor (0.875). That's because they started life as a tool to evaluate hash table designs and only later took a turn toward also trying to evaluate hash table libraries (there's a tension in the project's goals here). FNV-1a is not a fast hash function, so askl_map should have quite an advantage if it is allowed to use its default. What I'd like to do here is to run the benchmarks once with askl_map conforming to those requirements and again with each library using its defaults. Then you can review both results.

I'm not sure how I'll handling merging the PR. Maybe it would be best for me to merge it into a new branch? The issue here is that I wasn't planning to update the 2024 write-up (which would take a lot of work) anytime soon, and the write-up points to the repo as its source code.

@JacksonAllan

JacksonAllan commented Jun 9, 2026

Copy link
Copy Markdown
Owner

I finally found some time to run the benchmarks.

I included ASKL's map, Verstable, khash, khashl, and the two maps from M*LIB, which - besides khashl - are the hash tables that I test in 2024 and that also appeared in your own benchmarks. Most of these hash tables are the versions that were current in 2024 (i.e. I haven't updated them to the most recent versions).

I also accidentally included boost::unordered_flat_map instead of Abseil. But Boost was the top performer in 2024, so it's a good yardstick.

I used the shim you provided, unmodified. Originally I planned to run the benchmarks once with all hash tables - including ASKL - using the same hash functions (Murmur3 mixer for integers and FNV-1a for strings) and again with all hash tables using their default hash functions, as I explained earlier. However, I didn't end up doing that for reasons that will become clear below. Instead, I tested with all tables except ASKL using Murmer3 mixer/FNV-1a and ASKL using its defaults. This means that ASKL should have an advantage here because neither the Murmer3 mixer or FNV-1a are fast hash functions.

Note that khashl - or at least the version I tested (from sometime in 2025, if I recall correctly) - doesn't provide an iteration API, so for that test it does nothing. That's why it looks like it's much faster than the other tables for iteration. In actuality, Boost has the fastest iteration here, followed by Verstable.

Here's the results I get (GCC 12.1 via MinGW, -O3, AMD Ryzen 7 5800H, 16 GB RAM):

askl_200_000_keys.html
askl_2_000_000_keys.html
askl_20_000_000_keys.html

askl_results

Unfortunately, ASKL doesn't seem to be performing very well in these benchmarks, overall. That's why I didn't bother modifying it to use the slower hash functions, like the other tables, or to see how it competes with the other tables when they are allowed to use their (presumably faster) defaults. In those cases, I'd expect ASKL to do a little bit worse than shown here.

These results are rather different from the results you got in your own benchmarks, where ASKL was much more competitive. That could be due, in part, to the datatypes and functionality tested. In the string key benchmarks for insert, replace, look up existing, and look up nonexisting, ASKL isn't actually doing too badly here.

One oddity I noticed was a big spike in the 32-bit integer key, 32-bit value: Time to replace 1,000 existing keys with N keys in the table/200,00-keys benchmark at around 110,000 keys. I actually repeated that benchmark three times just to make sure the spike wasn't due to some background system activity.

@RaphaelPrevost

Copy link
Copy Markdown
Author

Thank you very much for sharing these results, this is super interesting because on my machine the results are quite different, look at this 200.000 keys run :

Screenshot 2026-06-10 at 08 59 18

Full run:
result_2026-06-10T05_51_46.html

Compiled with: g++ -I. -std=c++20 -O2 -flto=auto -DNDEBUG -Wall -Wpedantic main.cpp

So now I wonder if I'm doing something that's cheap on ARM64 but costly on x86_64. If you have the time, I would be very grateful if you tried to run my python/hyperfine benchmark on your machine, so we can see if the difference is arch-related. I'm also gonna try to run both benchmarks on a x86_64 box on my side.

@RaphaelPrevost

Copy link
Copy Markdown
Author

OK, I did some digging and I think the bad performance may be related to MinGW's pthread implementation. If I'm correct, compiling with -DHAS_ATOMICS should significantly improve performance. I identified the reason for the weird spike, I need to add a specific code path for the "upsert" function map_set so it avoids triggering a resize when the key already exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants