diff --git a/folly/concurrency/test/ConcurrentHashMapBench.cpp b/folly/concurrency/test/ConcurrentHashMapBench.cpp new file mode 100644 index 00000000000..23bee1e6cbd --- /dev/null +++ b/folly/concurrency/test/ConcurrentHashMapBench.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include + +DEFINE_int32(reps, 10, "number of reps"); +DEFINE_int32(ops, 1000 * 1000, "number of operations per rep"); +DEFINE_int64(size, 10 * 1000 * 1000, "size"); + +template +inline uint64_t run_once(int nthr, const Func& fn, const EndFunc& endFn) { + folly::test::Barrier b(nthr + 1); + std::vector thr(nthr); + for (int tid = 0; tid < nthr; ++tid) { + thr[tid] = std::thread([&, tid] { + b.wait(); + b.wait(); + fn(tid); + }); + } + b.wait(); + // begin time measurement + auto tbegin = std::chrono::steady_clock::now(); + b.wait(); + /* wait for completion */ + for (int i = 0; i < nthr; ++i) { + thr[i].join(); + } + /* end time measurement */ + auto tend = std::chrono::steady_clock::now(); + endFn(); + return std::chrono::duration_cast(tend - tbegin) + .count(); +} + +template +uint64_t runBench(const std::string& name, int ops, const RepFunc& repFn) { + int reps = FLAGS_reps; + uint64_t min = UINTMAX_MAX; + uint64_t max = 0; + uint64_t sum = 0; + + repFn(); // sometimes first run is outlier + for (int r = 0; r < reps; ++r) { + uint64_t dur = repFn(); + sum += dur; + min = std::min(min, dur); + max = std::max(max, dur); + // if each rep takes too long run at least 3 reps + const uint64_t minute = 60000000000UL; + if (sum > minute && r >= 2) { + reps = r + 1; + break; + } + } + + const std::string unit = " ns"; + uint64_t avg = sum / reps; + uint64_t res = min; + std::cout << name; + std::cout << " " << std::setw(4) << (max + ops / 2) / ops << unit; + std::cout << " " << std::setw(4) << (avg + ops / 2) / ops << unit; + std::cout << " " << std::setw(4) << (min + ops / 2) / ops << unit; + std::cout << std::endl; + return res; +} + +uint64_t bench_ctor_dtor( + const int nthr, const int size, const std::string& name) { + int ops = FLAGS_ops; + auto repFn = [&] { + auto fn = [&](int) { + for (int i = 0; i < ops; ++i) { + folly::ConcurrentHashMap m; + for (int j = 0; j < size; ++j) { + m.insert(j, j); + } + } + }; + auto endfn = [&] {}; + return run_once(nthr, fn, endfn); + }; + return runBench(name, ops, repFn); +} + +uint64_t bench_find( + const int nthr, const bool sameItem, const std::string& name) { + int ops = FLAGS_ops; + folly::ConcurrentHashMap m; + for (int j = 0; j < FLAGS_size; ++j) { + m.insert(j, j); + } + int key = FLAGS_size / 2; + auto repFn = [&] { + auto fn = [&](int) { + if (sameItem) { + for (int i = 0; i < ops; ++i) { + m.find(key); + } + } else { + for (int i = 0; i < ops; ++i) { + m.find(i); + } + } + }; + auto endfn = [&] {}; + return run_once(nthr, fn, endfn); + }; + return runBench(name, ops, repFn); +} + +uint64_t bench_iter(const int nthr, int size, const std::string& name) { + int reps = size == 0 ? 1000000 : size < 1000000 ? 1000000 / size : 1; + int ops = size == 0 ? reps : size * reps; + folly::ConcurrentHashMap m; + for (int j = 0; j < size; ++j) { + m.insert(j, j); + } + auto repFn = [&] { + auto fn = [&](int) { + for (int i = 0; i < reps; ++i) { + for (auto it = m.begin(); it != m.end(); ++it) { + } + } + }; + auto endfn = [&] {}; + return run_once(nthr, fn, endfn); + }; + return runBench(name, ops, repFn); +} + +uint64_t bench_begin(const int nthr, int size, const std::string& name) { + int ops = FLAGS_ops; + folly::ConcurrentHashMap m; + for (int j = 0; j < size; ++j) { + m.insert(j, j); + } + auto repFn = [&] { + auto fn = [&](int) { + for (int i = 0; i < ops; ++i) { + auto it = m.begin(); + } + }; + auto endfn = [&] {}; + return run_once(nthr, fn, endfn); + }; + return runBench(name, ops, repFn); +} + +uint64_t bench_empty(const int nthr, int size, const std::string& name) { + int ops = FLAGS_ops; + folly::ConcurrentHashMap m; + for (int j = 0; j < size; ++j) { + m.insert(j, j); + } + auto repFn = [&] { + auto fn = [&](int) { + for (int i = 0; i < ops; ++i) { + m.empty(); + } + }; + auto endfn = [&] {}; + return run_once(nthr, fn, endfn); + }; + return runBench(name, ops, repFn); +} + +uint64_t bench_size(const int nthr, int size, const std::string& name) { + int ops = FLAGS_ops; + folly::ConcurrentHashMap m; + for (int j = 0; j < size; ++j) { + m.insert(j, j); + } + auto repFn = [&] { + auto fn = [&](int) { + for (int i = 0; i < ops; ++i) { + m.size(); + } + }; + auto endfn = [&] {}; + return run_once(nthr, fn, endfn); + }; + return runBench(name, ops, repFn); +} + +void dottedLine() { + std::cout << ".............................................................." + << std::endl; +} + +void benches() { + std::cout << "==============================================================" + << std::endl; + std::cout << "Test name Max time Avg time Min time" + << std::endl; + for (int nthr : {1, 10}) { + std::cout << "========================= " << std::setw(2) << nthr + << " threads" + << " =========================" << std::endl; + bench_ctor_dtor(nthr, 0, "CHM ctor/dtor -- empty "); + bench_ctor_dtor(nthr, 1, "CHM ctor/dtor -- 1 item "); + dottedLine(); + bench_find(nthr, false, "CHM find() -- 10M items "); + bench_find(nthr, true, "CHM find() -- 1 of 10M items "); + dottedLine(); + bench_begin(nthr, 0, "CHM begin() -- empty "); + bench_begin(nthr, 1, "CHM begin() -- 1 item "); + bench_begin(nthr, 10000, "CHM begin() -- 10K items "); + bench_begin(nthr, 10000000, "CHM begin() -- 10M items "); + dottedLine(); + bench_iter(nthr, 0, "CHM iterate -- empty "); + bench_iter(nthr, 1, "CHM iterate -- 1 item "); + bench_iter(nthr, 10, "CHM iterate -- 10 items "); + bench_iter(nthr, 100, "CHM iterate -- 100 items "); + bench_iter(nthr, 1000, "CHM iterate -- 1K items "); + bench_iter(nthr, 10000, "CHM iterate -- 10K items "); + bench_iter(nthr, 100000, "CHM iterate -- 100K items "); + bench_iter(nthr, 1000000, "CHM iterate -- 1M items "); + bench_iter(nthr, 10000000, "CHM iterate -- 10M items "); + dottedLine(); + bench_empty(nthr, 0, "CHM empty() -- empty "); + bench_empty(nthr, 1, "CHM empty() -- 1 item "); + bench_empty(nthr, 10000, "CHM empty() -- 10K items "); + bench_empty(nthr, 10000000, "CHM empty() -- 10M items "); + dottedLine(); + bench_size(nthr, 0, "CHM size() -- empty "); + bench_size(nthr, 1, "CHM size() -- 1 item "); + bench_size(nthr, 10, "CHM size() -- 10 items "); + bench_size(nthr, 100, "CHM size() -- 100 items "); + bench_size(nthr, 1000, "CHM size() -- 1K items "); + bench_size(nthr, 10000, "CHM size() -- 10K items "); + bench_size(nthr, 100000, "CHM size() -- 100K items "); + bench_size(nthr, 1000000, "CHM size() -- 1M items "); + bench_size(nthr, 10000000, "CHM size() -- 10M items "); + } + std::cout << "==============================================================" + << std::endl; +} + +int main() { + benches(); +}