/* Compare speed of hash functions on fixed random byte strings. Output: speed/{hash_name}.out, CSV, one row per input length. Build example: cc -O3 -I. -c src/joaat/hash.c -o joaat.o cc -O3 -I. -c src/xxHash/xxhash.c -o xxhash.o g++ -O3 -std=c++17 -I. -Isrc/cityhash/src speed.cpp \ src/cityhash/src/city.cc joaat.o xxhash.o -o speed_test Run: ./speed_test [runs=7] [target_mib_per_run=32] [max_iters=200000] */ #include #include #include #include #include #include #include #include #include #include #include #include #include "src/cityhash/src/city.h" #include "src/joaat/hash.h" #include "src/xxHash/xxhash.h" namespace fs = std::filesystem; using Clock = std::chrono::steady_clock; using HashFn = uint64_t (*)(const void *, size_t); struct HashCase { std::string_view name; HashFn fn; }; static uint64_t hash_xxh32(const void *data, size_t len) { return XXH32(data, len, 0); } static uint64_t hash_xxh64(const void *data, size_t len) { return XXH64(data, len, 0); } static uint64_t hash_xxh3_64(const void *data, size_t len) { return XXH3_64bits(data, len); } static uint64_t hash_city32(const void *data, size_t len) { return CityHash32(static_cast(data), len); } static uint64_t hash_city64(const void *data, size_t len) { return CityHash64(static_cast(data), len); } static uint64_t hash_joaat(const void *data, size_t len) { return joaat_hash_bytes(data, len); } static size_t iterations_for(size_t len, size_t target_bytes, size_t max_iters) { if (len == 0) { return max_iters; } size_t iters = target_bytes / len; iters = std::max(iters, 128); iters = std::min(iters, max_iters); return iters; } static double median(std::vector values) { const size_t mid = values.size() / 2; std::nth_element(values.begin(), values.begin() + mid, values.end()); if (values.size() % 2 != 0) { return values[mid]; } const double hi = values[mid]; std::nth_element(values.begin(), values.begin() + mid - 1, values.end()); return (values[mid - 1] + hi) / 2.0; } static std::vector random_bytes(size_t len) { std::vector data(len); std::mt19937_64 rng(0x415253565f686173ULL ^ len); for (unsigned char &byte : data) { byte = static_cast(rng() & 0xffu); } return data; } static uint64_t bench_once(HashFn fn, const std::vector &data, size_t iters) { uint64_t sink = 0; const void *ptr = data.data(); const size_t len = data.size(); for (size_t i = 0; i < iters; ++i) { sink ^= fn(ptr, len) + 0x9e3779b97f4a7c15ULL + (sink << 6) + (sink >> 2); } return sink; } int main(int argc, char **argv) { const int runs = argc > 1 ? std::max(1, std::atoi(argv[1])) : 7; const size_t target_mib = argc > 2 ? std::max(1, std::atoi(argv[2])) : 32; const size_t max_iters = argc > 3 ? std::max(1, std::atoi(argv[3])) : 200000; const size_t target_bytes = target_mib * 1024ULL * 1024ULL; const std::vector lengths = { 1, 4, 8, 16, 32, 64, 128, 256, 1024, 4096, 16384, 65536, 1048576, }; const std::vector hashes = { {"xxh32", hash_xxh32}, {"xxh64", hash_xxh64}, {"xxh3_64", hash_xxh3_64}, {"city32", hash_city32}, {"city64", hash_city64}, {"joaat", hash_joaat}, }; const fs::path out_dir = fs::path(__FILE__).stem(); fs::create_directories(out_dir); volatile uint64_t global_sink = 0; for (const HashCase &hash : hashes) { const fs::path out_path = out_dir / (std::string(hash.name) + ".out"); std::ofstream out(out_path); if (!out) { std::cerr << "failed to open " << out_path << '\n'; return 1; } out << "hash,length,median_ns_per_hash,median_gib_per_s,iterations,runs,total_bytes_per_run,sink\n"; out << std::fixed << std::setprecision(6); for (size_t len : lengths) { const std::vector data = random_bytes(len); const size_t iters = iterations_for(len, target_bytes, max_iters); std::vector ns_per_hash; std::vector gib_per_s; uint64_t sink = 0; /* Warmup outside measured runs. */ sink ^= bench_once(hash.fn, data, std::min(iters, 1024)); for (int run = 0; run < runs; ++run) { const auto start = Clock::now(); sink ^= bench_once(hash.fn, data, iters); const auto stop = Clock::now(); const double seconds = std::chrono::duration(stop - start).count(); const double bytes = static_cast(len) * static_cast(iters); ns_per_hash.push_back(seconds * 1e9 / static_cast(iters)); gib_per_s.push_back(bytes / seconds / (1024.0 * 1024.0 * 1024.0)); } global_sink ^= sink; out << hash.name << ',' << len << ',' << median(ns_per_hash) << ',' << median(gib_per_s) << ',' << iters << ',' << runs << ',' << (len * iters) << ',' << sink << '\n'; } } std::cerr << "wrote " << out_dir << "/*.out; sink=" << global_sink << '\n'; return 0; }