folly/folly/concurrency/test/ConcurrentHashMapBench.cpp

/*
 * Copyright (c) Meta Platforms, Inc. and 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 <folly/concurrency/ConcurrentHashMap.h>

#include <iomanip>
#include <iostream>
#include <thread>

#include <folly/synchronization/test/Barrier.h>

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 <typename Func, typename EndFunc>
inline uint64_t run_once(int nthr, const Func& fn, const EndFunc& endFn) {
  folly::test::Barrier b(nthr + 1);
  std::vector<std::thread> 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<std::chrono::nanoseconds>(tend - tbegin)
      .count();
}

template <typename RepFunc>
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<int, int> 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<int, int> 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<int, int> 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<int, int> 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<int, int> 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<int, int> 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();
}