/*
 * 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/hash/Hash.h>
#include <folly/hash/MurmurHash.h>

#include <stdint.h>

#include <deque>
#include <random>
#include <string>
#include <vector>

#include <fmt/core.h>
#include <glog/logging.h>

#include <folly/Benchmark.h>
#include <folly/Preprocessor.h>
#include <folly/lang/Keep.h>
#include <folly/portability/GFlags.h>

extern "C" FOLLY_KEEP uint64_t
check_folly_spooky_hash_v2_hash_32(void const* data, size_t size) {
  return folly::hash::SpookyHashV2::Hash32(data, size, 0);
}

extern "C" FOLLY_KEEP uint64_t
check_folly_spooky_hash_v2_hash_64(void const* data, size_t size) {
  return folly::hash::SpookyHashV2::Hash64(data, size, 0);
}

namespace detail {

std::vector<uint8_t> randomBytes(size_t n) {
  std::vector<uint8_t> ret(n);
  std::default_random_engine rng(1729); // Deterministic seed.
  std::uniform_int_distribution<uint16_t> dist(0, 255);
  std::generate(ret.begin(), ret.end(), [&]() { return dist(rng); });
  return ret;
}

std::vector<uint8_t> benchData = randomBytes(1 << 20); // 1MiB, fits in cache.

template <class Hasher>
void bmHasher(Hasher hasher, size_t k, size_t iters) {
  CHECK_LE(k, benchData.size());
  for (size_t i = 0, pos = 0; i < iters; ++i, ++pos) {
    if (pos == benchData.size() - k + 1) {
      pos = 0;
    }
    folly::doNotOptimizeAway(hasher(benchData.data() + pos, k));
  }
}

template <class Hasher>
void addHashBenchmark(const std::string& name) {
  static std::deque<std::string> names;

  for (size_t k = 1; k < 16; ++k) {
    names.emplace_back(fmt::format("{}: k={}", name, k));
    folly::addBenchmark(__FILE__, names.back().c_str(), [=](unsigned iters) {
      Hasher hasher;
      bmHasher(hasher, k, iters);
      return iters;
    });
  }

  for (size_t i = 0; i < 16; ++i) {
    auto k = size_t(1) << i;
    names.emplace_back(fmt::format("{}: k=2^{}", name, i));
    folly::addBenchmark(__FILE__, names.back().c_str(), [=](unsigned iters) {
      Hasher hasher;
      bmHasher(hasher, k, iters);
      return iters;
    });
  }

  /* Draw line. */
  folly::addBenchmark(__FILE__, "-", []() { return 0; });
}

struct SpookyHashV2 {
  uint64_t operator()(const uint8_t* data, size_t size) const {
    return folly::hash::SpookyHashV2::Hash64(data, size, 0);
  }
};

struct FNV64 {
  uint64_t operator()(const uint8_t* data, size_t size) const {
    return folly::hash::fnv64_buf(data, size);
  }
};

struct MurmurHash {
  uint64_t operator()(const uint8_t* data, size_t size) const {
    return folly::hash::murmurHash64(
        reinterpret_cast<const char*>(data), size, 0);
  }
};

} // namespace detail

int main(int argc, char** argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, true);
  google::InitGoogleLogging(argv[0]);

  std::deque<std::string> names; // Backing for benchmark names.

#define BENCHMARK_HASH(HASHER) \
  detail::addHashBenchmark<detail::HASHER>(FOLLY_PP_STRINGIZE(HASHER));

  BENCHMARK_HASH(SpookyHashV2);
  BENCHMARK_HASH(FNV64);
  BENCHMARK_HASH(MurmurHash);

#undef BENCHMARK_HASH

  folly::runBenchmarks();

  return 0;
}

#if 0
Intel(R) Xeon(R) Gold 6138 CPU @ 2.00GHz
$ hash_benchmark --bm_min_usec=100000
============================================================================
fbcode/folly/hash/test/HashBenchmark.cpp     relative  time/iter   iters/s
============================================================================
SpookyHashV2: k=1                                           7.36ns   135.94M
SpookyHashV2: k=2                                           7.47ns   133.91M
SpookyHashV2: k=3                                           7.74ns   129.16M
SpookyHashV2: k=4                                           7.14ns   140.06M
SpookyHashV2: k=5                                           7.52ns   133.05M
SpookyHashV2: k=6                                           7.84ns   127.58M
SpookyHashV2: k=7                                           8.11ns   123.34M
SpookyHashV2: k=8                                           7.24ns   138.09M
SpookyHashV2: k=9                                           7.32ns   136.57M
SpookyHashV2: k=10                                          7.62ns   131.27M
SpookyHashV2: k=11                                          7.84ns   127.58M
SpookyHashV2: k=12                                          8.07ns   123.90M
SpookyHashV2: k=13                                          7.85ns   127.40M
SpookyHashV2: k=14                                          8.02ns   124.76M
SpookyHashV2: k=15                                          8.24ns   121.42M
SpookyHashV2: k=2^0                                         7.18ns   139.28M
SpookyHashV2: k=2^1                                         7.65ns   130.68M
SpookyHashV2: k=2^2                                         7.24ns   138.16M
SpookyHashV2: k=2^3                                         7.40ns   135.15M
SpookyHashV2: k=2^4                                        13.91ns    71.91M
SpookyHashV2: k=2^5                                        14.06ns    71.11M
SpookyHashV2: k=2^6                                        21.83ns    45.81M
SpookyHashV2: k=2^7                                        37.35ns    26.77M
SpookyHashV2: k=2^8                                        47.34ns    21.12M
SpookyHashV2: k=2^9                                        65.66ns    15.23M
SpookyHashV2: k=2^10                                       99.14ns    10.09M
SpookyHashV2: k=2^11                                      172.31ns     5.80M
SpookyHashV2: k=2^12                                      314.87ns     3.18M
SpookyHashV2: k=2^13                                      596.77ns     1.68M
SpookyHashV2: k=2^14                                        1.16us   860.42K
SpookyHashV2: k=2^15                                        2.33us   428.39K
----------------------------------------------------------------------------
FNV64: k=1                                                  1.67ns   597.73M
FNV64: k=2                                                  2.16ns   463.65M
FNV64: k=3                                                  2.98ns   335.84M
FNV64: k=4                                                  3.34ns   299.81M
FNV64: k=5                                                  3.94ns   253.49M
FNV64: k=6                                                  4.50ns   222.04M
FNV64: k=7                                                  5.10ns   196.27M
FNV64: k=8                                                  4.29ns   233.13M
FNV64: k=9                                                  5.16ns   193.88M
FNV64: k=10                                                 5.70ns   175.35M
FNV64: k=11                                                 6.28ns   159.25M
FNV64: k=12                                                 7.32ns   136.54M
FNV64: k=13                                                 8.01ns   124.80M
FNV64: k=14                                                 8.80ns   113.60M
FNV64: k=15                                                 9.42ns   106.17M
FNV64: k=2^0                                                1.66ns   600.75M
FNV64: k=2^1                                                2.21ns   452.85M
FNV64: k=2^2                                                3.40ns   294.24M
FNV64: k=2^3                                                4.33ns   231.13M
FNV64: k=2^4                                                9.42ns   106.15M
FNV64: k=2^5                                               22.39ns    44.66M
FNV64: k=2^6                                               50.47ns    19.81M
FNV64: k=2^7                                              127.87ns     7.82M
FNV64: k=2^8                                              279.80ns     3.57M
FNV64: k=2^9                                              589.47ns     1.70M
FNV64: k=2^10                                               1.22us   817.45K
FNV64: k=2^11                                               2.46us   406.98K
FNV64: k=2^12                                               4.92us   203.27K
FNV64: k=2^13                                               9.84us   101.61K
FNV64: k=2^14                                              19.66us    50.85K
FNV64: k=2^15                                              39.65us    25.22K
----------------------------------------------------------------------------
MurmurHash: k=1                                             1.92ns   520.45M
MurmurHash: k=2                                             2.22ns   451.21M
MurmurHash: k=3                                             2.28ns   437.75M
MurmurHash: k=4                                             1.98ns   504.77M
MurmurHash: k=5                                             2.18ns   458.61M
MurmurHash: k=6                                             2.46ns   406.96M
MurmurHash: k=7                                             2.52ns   396.89M
MurmurHash: k=8                                             2.84ns   352.24M
MurmurHash: k=9                                             3.63ns   275.50M
MurmurHash: k=10                                            3.88ns   257.82M
MurmurHash: k=11                                            4.03ns   248.11M
MurmurHash: k=12                                            3.72ns   268.52M
MurmurHash: k=13                                            3.91ns   255.67M
MurmurHash: k=14                                            4.20ns   238.10M
MurmurHash: k=15                                            4.41ns   226.70M
MurmurHash: k=2^0                                           1.87ns   533.86M
MurmurHash: k=2^1                                           2.17ns   460.14M
MurmurHash: k=2^2                                           1.96ns   510.29M
MurmurHash: k=2^3                                           2.78ns   359.29M
MurmurHash: k=2^4                                           3.84ns   260.18M
MurmurHash: k=2^5                                           5.22ns   191.49M
MurmurHash: k=2^6                                           8.99ns   111.18M
MurmurHash: k=2^7                                          17.05ns    58.63M
MurmurHash: k=2^8                                          32.43ns    30.84M
MurmurHash: k=2^9                                          70.59ns    14.17M
MurmurHash: k=2^10                                        147.21ns     6.79M
MurmurHash: k=2^11                                        301.94ns     3.31M
MurmurHash: k=2^12                                        614.43ns     1.63M
MurmurHash: k=2^13                                          1.23us   810.19K
MurmurHash: k=2^14                                          2.47us   405.39K
MurmurHash: k=2^15                                          4.94us   202.32K
----------------------------------------------------------------------------
#endif
