folly/folly/test/ConvBenchmark.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/Conv.h>

#include <array>
#include <limits>
#include <stdexcept>

#include <boost/lexical_cast.hpp>

#include <folly/Benchmark.h>
#include <folly/CppAttributes.h>
#include <folly/container/Foreach.h>
#include <folly/lang/ToAscii.h>

using namespace std;
using namespace folly;

// Android doesn't support std::to_string so just use a placeholder there.
#ifdef __ANDROID__
#define FOLLY_RANGE_CHECK_TO_STRING(x) std::string("N/A")
#else
#define FOLLY_RANGE_CHECK_TO_STRING(x) std::to_string(x)
#endif

namespace folly {
namespace conv_bench_detail {

// Keep this data global and non-const, so the compiler cannot make
// any assumptions about the actual values at compile time

uint64_t uint64Num[] = {
    0,
    1ULL,
    12ULL,
    123ULL,
    1234ULL,
    12345ULL,
    123456ULL,
    1234567ULL,
    12345678ULL,
    123456789ULL,
    1234567890ULL,
    12345678901ULL,
    123456789012ULL,
    1234567890123ULL,
    12345678901234ULL,
    123456789012345ULL,
    1234567890123456ULL,
    12345678901234567ULL,
    123456789012345678ULL,
    1234567890123456789ULL,
    12345678901234567890ULL,
};

int64_t int64Pos[] = {
    0,
    1LL,
    12LL,
    123LL,
    1234LL,
    12345LL,
    123456LL,
    1234567LL,
    12345678LL,
    123456789LL,
    1234567890LL,
    12345678901LL,
    123456789012LL,
    1234567890123LL,
    12345678901234LL,
    123456789012345LL,
    1234567890123456LL,
    12345678901234567LL,
    123456789012345678LL,
    1234567890123456789LL,
};

int64_t int64Neg[] = {
    0,
    -1LL,
    -12LL,
    -123LL,
    -1234LL,
    -12345LL,
    -123456LL,
    -1234567LL,
    -12345678LL,
    -123456789LL,
    -1234567890LL,
    -12345678901LL,
    -123456789012LL,
    -1234567890123LL,
    -12345678901234LL,
    -123456789012345LL,
    -1234567890123456LL,
    -12345678901234567LL,
    -123456789012345678LL,
    -1234567890123456789LL,
};

#if FOLLY_HAVE_INT128_T

unsigned __int128 uint128Num[] = {
    0,
    static_cast<unsigned __int128>(1) << 0,
    static_cast<unsigned __int128>(1) << 4,
    static_cast<unsigned __int128>(1) << 7,
    static_cast<unsigned __int128>(1) << 10,
    static_cast<unsigned __int128>(1) << 14,
    static_cast<unsigned __int128>(1) << 17,
    static_cast<unsigned __int128>(1) << 20,
    static_cast<unsigned __int128>(1) << 24,
    static_cast<unsigned __int128>(1) << 27,
    static_cast<unsigned __int128>(1) << 30,
    static_cast<unsigned __int128>(1) << 34,
    static_cast<unsigned __int128>(1) << 37,
    static_cast<unsigned __int128>(1) << 40,
    static_cast<unsigned __int128>(1) << 44,
    static_cast<unsigned __int128>(1) << 47,
    static_cast<unsigned __int128>(1) << 50,
    static_cast<unsigned __int128>(1) << 54,
    static_cast<unsigned __int128>(1) << 57,
    static_cast<unsigned __int128>(1) << 60,
    static_cast<unsigned __int128>(1) << 64,
    static_cast<unsigned __int128>(1) << 67,
    static_cast<unsigned __int128>(1) << 70,
    static_cast<unsigned __int128>(1) << 74,
    static_cast<unsigned __int128>(1) << 77,
    static_cast<unsigned __int128>(1) << 80,
    static_cast<unsigned __int128>(1) << 84,
    static_cast<unsigned __int128>(1) << 87,
    static_cast<unsigned __int128>(1) << 90,
    static_cast<unsigned __int128>(1) << 94,
    static_cast<unsigned __int128>(1) << 97,
    static_cast<unsigned __int128>(1) << 100,
    static_cast<unsigned __int128>(1) << 103,
    static_cast<unsigned __int128>(1) << 107,
    static_cast<unsigned __int128>(1) << 110,
    static_cast<unsigned __int128>(1) << 113,
    static_cast<unsigned __int128>(1) << 117,
    static_cast<unsigned __int128>(1) << 120,
    static_cast<unsigned __int128>(1) << 123,
    static_cast<unsigned __int128>(1) << 127,
};

__int128 int128Pos[] = {
    0,
    static_cast<__int128>(1) << 0,
    static_cast<__int128>(1) << 4,
    static_cast<__int128>(1) << 7,
    static_cast<__int128>(1) << 10,
    static_cast<__int128>(1) << 14,
    static_cast<__int128>(1) << 17,
    static_cast<__int128>(1) << 20,
    static_cast<__int128>(1) << 24,
    static_cast<__int128>(1) << 27,
    static_cast<__int128>(1) << 30,
    static_cast<__int128>(1) << 34,
    static_cast<__int128>(1) << 37,
    static_cast<__int128>(1) << 40,
    static_cast<__int128>(1) << 44,
    static_cast<__int128>(1) << 47,
    static_cast<__int128>(1) << 50,
    static_cast<__int128>(1) << 54,
    static_cast<__int128>(1) << 57,
    static_cast<__int128>(1) << 60,
    static_cast<__int128>(1) << 64,
    static_cast<__int128>(1) << 67,
    static_cast<__int128>(1) << 70,
    static_cast<__int128>(1) << 74,
    static_cast<__int128>(1) << 77,
    static_cast<__int128>(1) << 80,
    static_cast<__int128>(1) << 84,
    static_cast<__int128>(1) << 87,
    static_cast<__int128>(1) << 90,
    static_cast<__int128>(1) << 94,
    static_cast<__int128>(1) << 97,
    static_cast<__int128>(1) << 100,
    static_cast<__int128>(1) << 103,
    static_cast<__int128>(1) << 107,
    static_cast<__int128>(1) << 110,
    static_cast<__int128>(1) << 113,
    static_cast<__int128>(1) << 117,
    static_cast<__int128>(1) << 120,
    static_cast<__int128>(1) << 123,
    static_cast<__int128>(3) << 125,
};

__int128 int128Neg[] = {
    0,
    -(static_cast<__int128>(1) << 0),
    -(static_cast<__int128>(1) << 4),
    -(static_cast<__int128>(1) << 7),
    -(static_cast<__int128>(1) << 10),
    -(static_cast<__int128>(1) << 14),
    -(static_cast<__int128>(1) << 17),
    -(static_cast<__int128>(1) << 20),
    -(static_cast<__int128>(1) << 24),
    -(static_cast<__int128>(1) << 27),
    -(static_cast<__int128>(1) << 30),
    -(static_cast<__int128>(1) << 34),
    -(static_cast<__int128>(1) << 37),
    -(static_cast<__int128>(1) << 40),
    -(static_cast<__int128>(1) << 44),
    -(static_cast<__int128>(1) << 47),
    -(static_cast<__int128>(1) << 50),
    -(static_cast<__int128>(1) << 54),
    -(static_cast<__int128>(1) << 57),
    -(static_cast<__int128>(1) << 60),
    -(static_cast<__int128>(1) << 64),
    -(static_cast<__int128>(1) << 67),
    -(static_cast<__int128>(1) << 70),
    -(static_cast<__int128>(1) << 74),
    -(static_cast<__int128>(1) << 77),
    -(static_cast<__int128>(1) << 80),
    -(static_cast<__int128>(1) << 84),
    -(static_cast<__int128>(1) << 87),
    -(static_cast<__int128>(1) << 90),
    -(static_cast<__int128>(1) << 94),
    -(static_cast<__int128>(1) << 97),
    -(static_cast<__int128>(1) << 100),
    -(static_cast<__int128>(1) << 103),
    -(static_cast<__int128>(1) << 107),
    -(static_cast<__int128>(1) << 110),
    -(static_cast<__int128>(1) << 113),
    -(static_cast<__int128>(1) << 117),
    -(static_cast<__int128>(1) << 120),
    -(static_cast<__int128>(1) << 123),
    -(static_cast<__int128>(3) << 125),
};

#endif
} // namespace conv_bench_detail
} // namespace folly

using namespace folly::conv_bench_detail;

namespace {

template <typename T>
void checkArrayIndex(const T& array, size_t index) {
  DCHECK_LT(index, sizeof(array) / sizeof(array[0]));
}
} // namespace

////////////////////////////////////////////////////////////////////////////////
// Benchmarks for ASCII to int conversion
////////////////////////////////////////////////////////////////////////////////

static int64_t handwrittenAtoi(const char* start, const char* end) {
  bool positive = true;
  int64_t retVal = 0;

  if (start == end) {
    throw std::runtime_error("empty string");
  }

  while (start < end && isspace(*start)) {
    ++start;
  }

  switch (*start) {
    case '-':
      positive = false;
      [[fallthrough]];
    case '+':
      ++start;
      [[fallthrough]];
    default:
      break;
  }

  while (start < end && *start >= '0' && *start <= '9') {
    auto const newRetVal = retVal * 10 + (*start++ - '0');
    if (newRetVal < retVal) {
      throw std::runtime_error("overflow");
    }
    retVal = newRetVal;
  }

  if (start != end) {
    throw std::runtime_error("extra chars at the end");
  }

  return positive ? retVal : -retVal;
}

static StringPiece pc1 = "1234567890123456789";

void handwrittenAtoiMeasure(unsigned int n, unsigned int digits) {
  auto p = pc1.subpiece(pc1.size() - digits, digits);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(handwrittenAtoi(p.begin(), p.end()));
  }
}

void follyAtoiMeasure(unsigned int n, unsigned int digits) {
  auto p = pc1.subpiece(pc1.size() - digits, digits);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(folly::to<int64_t>(p.begin(), p.end()));
  }
}

void clibAtoiMeasure(unsigned int n, unsigned int digits) {
  auto p = pc1.subpiece(pc1.size() - digits, digits);
  assert(*p.end() == 0);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(atoll(p.begin()));
  }
}

void lexicalCastMeasure(unsigned int n, unsigned int digits) {
  auto p = pc1.subpiece(pc1.size() - digits, digits);
  assert(*p.end() == 0);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(boost::lexical_cast<uint64_t>(p.begin()));
  }
}

// Benchmarks for unsigned to string conversion, raw

unsigned u64ToAsciiTable(uint64_t value, char* dst) {
  static const char digits[201] =
      "00010203040506070809"
      "10111213141516171819"
      "20212223242526272829"
      "30313233343536373839"
      "40414243444546474849"
      "50515253545556575859"
      "60616263646566676869"
      "70717273747576777879"
      "80818283848586878889"
      "90919293949596979899";

  uint32_t const length = to_ascii_size_decimal(value);
  uint32_t next = length - 1;
  while (value >= 100) {
    auto const i = (value % 100) * 2;
    value /= 100;
    dst[next] = digits[i + 1];
    dst[next - 1] = digits[i];
    next -= 2;
  }
  // Handle last 1-2 digits
  if (value < 10) {
    dst[next] = '0' + uint32_t(value);
  } else {
    auto i = uint32_t(value) * 2;
    dst[next] = digits[i + 1];
    dst[next - 1] = digits[i];
  }
  return length;
}

void u64ToAsciiTableBM(unsigned int n, size_t index) {
  checkArrayIndex(uint64Num, index);
  char buf[20];
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(u64ToAsciiTable(uint64Num[index] + (i % 8), buf));
  }
}

unsigned u64ToAsciiClassic(uint64_t value, char* dst) {
  // Write backwards.
  char* next = (char*)dst;
  char* start = next;
  do {
    *next++ = '0' + (value % 10);
    value /= 10;
  } while (value != 0);
  unsigned length = next - start;

  // Reverse in-place.
  next--;
  while (next > start) {
    char swap = *next;
    *next = *start;
    *start = swap;
    next--;
    start++;
  }
  return length;
}

void u64ToAsciiClassicBM(unsigned int n, size_t index) {
  checkArrayIndex(uint64Num, index);
  char buf[20];
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(u64ToAsciiClassic(uint64Num[index] + (i % 8), buf));
  }
}

void u64ToAsciiFollyBM(unsigned int n, size_t index) {
  checkArrayIndex(uint64Num, index);
  char buf[20];
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(to_ascii_decimal(buf, uint64Num[index] + (i % 8)));
  }
}

// Benchmark unsigned to string conversion

void u64ToStringClibMeasure(unsigned int n, size_t index) {
  // FOLLY_RANGE_CHECK_TO_STRING expands to std::to_string, except on Android
  // where std::to_string is not supported
  checkArrayIndex(uint64Num, index);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(
        FOLLY_RANGE_CHECK_TO_STRING(uint64Num[index] + (i % 8)).size());
  }
}

void u64ToStringFollyMeasure(unsigned int n, size_t index) {
  checkArrayIndex(uint64Num, index);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(to<std::string>(uint64Num[index] + (i % 8)).size());
  }
}

// Signed

void i64ToStringFollyMeasurePos(unsigned int n, size_t index) {
  checkArrayIndex(int64Pos, index);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(to<std::string>(int64Pos[index] + (i % 8)).size());
  }
}

void i64ToStringFollyMeasureNeg(unsigned int n, size_t index) {
  checkArrayIndex(int64Neg, index);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(to<std::string>(int64Neg[index] - (i % 8)).size());
  }
}

// Benchmark uitoa with string append

void u2aAppendClassicBM(unsigned int n, size_t index) {
  checkArrayIndex(uint64Num, index);
  string s;
  FOR_EACH_RANGE (i, 0, n) {
    // auto buf = &s.back() + 1;
    char buffer[20];
    s.append(buffer, u64ToAsciiClassic(uint64Num[index] + (i % 8), buffer));
    doNotOptimizeAway(s.size());
  }
}

void u2aAppendFollyBM(unsigned int n, size_t index) {
  checkArrayIndex(uint64Num, index);
  string s;
  FOR_EACH_RANGE (i, 0, n) {
    // auto buf = &s.back() + 1;
    char buffer[20];
    s.append(buffer, to_ascii_decimal(buffer, uint64Num[index] + (i % 8)));
    doNotOptimizeAway(s.size());
  }
}

template <class String>
struct StringIdenticalToBM {
  StringIdenticalToBM() {}
  void operator()(unsigned int n, size_t len) const {
    String s;
    BENCHMARK_SUSPEND {
      s.append(len, '0');
    }
    FOR_EACH_RANGE (i, 0, n) {
      String result = to<String>(s);
      doNotOptimizeAway(result.size());
    }
  }
};

template <class String>
struct StringVariadicToBM {
  StringVariadicToBM() {}
  void operator()(unsigned int n, size_t len) const {
    String s;
    BENCHMARK_SUSPEND {
      s.append(len, '0');
    }
    FOR_EACH_RANGE (i, 0, n) {
      String result = to<String>(s, nullptr);
      doNotOptimizeAway(result.size());
    }
  }
};

namespace folly {
namespace conv_bench_detail {

// Keep this data global and non-const, so the compiler cannot make
// any assumptions about the actual values at compile time

size_t bigInt = 11424545345345;
size_t smallInt = 104;
char someString[] = "this is some nice string";
char otherString[] = "this is a long string, so it's not so nice";
char reallyShort[] = "meh";
std::string stdString = "std::strings are very nice";
float fValue = 1.2355f;
double dValue = 345345345.435;
} // namespace conv_bench_detail
} // namespace folly

BENCHMARK(preallocateTestNoFloat, n) {
  for (size_t i = 0; i < n; ++i) {
    doNotOptimizeAway(
        to<std::string>(bigInt, someString, stdString, otherString).size());
    doNotOptimizeAway(to<std::string>(reallyShort, smallInt).size());
    doNotOptimizeAway(to<std::string>(bigInt, stdString).size());
    doNotOptimizeAway(
        to<std::string>(bigInt, stdString, dValue, otherString).size());
    doNotOptimizeAway(to<std::string>(bigInt, someString, reallyShort).size());
  }
}

BENCHMARK(preallocateTestFloat, n) {
  for (size_t i = 0; i < n; ++i) {
    doNotOptimizeAway(to<std::string>(stdString, ',', fValue, dValue).size());
    doNotOptimizeAway(to<std::string>(stdString, ',', dValue).size());
  }
}

namespace folly {
namespace conv_bench_detail {

// Keep this data global and non-const, so the compiler cannot make
// any assumptions about the actual values at compile time

int8_t i8s[] = {
    -(static_cast<int8_t>(1) << 4),
    static_cast<int8_t>(1) << 5,
    -(static_cast<int8_t>(1) << 6),
};

uint8_t u8s[] = {
    static_cast<uint8_t>(1) << 4,
    static_cast<uint8_t>(1) << 5,
    static_cast<uint8_t>(1) << 7,
};

int16_t i16s[] = {
    -(static_cast<int16_t>(1) << 8),
    static_cast<int16_t>(1) << 12,
    -(static_cast<int16_t>(1) << 14),
};

uint16_t u16s[] = {
    static_cast<uint16_t>(1) << 8,
    static_cast<uint16_t>(1) << 12,
    static_cast<uint16_t>(1) << 15,
};

int32_t i32s[] = {
    -(static_cast<int32_t>(1) << 16),
    static_cast<int32_t>(1) << 25,
    -(static_cast<int32_t>(1) << 30),
};

uint32_t u32s[] = {
    static_cast<uint32_t>(1) << 16,
    static_cast<uint32_t>(1) << 25,
    static_cast<uint32_t>(1) << 31,
};

int64_t i64s[] = {
    -(static_cast<int64_t>(1) << 32),
    static_cast<int64_t>(1) << 50,
    -(static_cast<int64_t>(1) << 62),
};

uint64_t u64s[] = {
    static_cast<uint64_t>(1) << 32,
    static_cast<uint64_t>(1) << 50,
    static_cast<uint64_t>(1) << 63,
};
} // namespace conv_bench_detail
} // namespace folly

BENCHMARK(preallocateTestInt8, n) {
  for (size_t i = 0; i < n; ++i) {
    doNotOptimizeAway(to<std::string>(
                          i8s[0],
                          ',',
                          u8s[0],
                          ',',
                          i8s[1],
                          ',',
                          u8s[1],
                          ',',
                          i8s[2],
                          ',',
                          u8s[2])
                          .size());
  }
}

BENCHMARK(preallocateTestInt16, n) {
  for (size_t i = 0; i < n; ++i) {
    doNotOptimizeAway(to<std::string>(
                          i16s[0],
                          ',',
                          u16s[0],
                          ',',
                          i16s[1],
                          ',',
                          u16s[1],
                          ',',
                          i16s[2],
                          ',',
                          u16s[2])
                          .size());
  }
}

BENCHMARK(preallocateTestInt32, n) {
  for (size_t i = 0; i < n; ++i) {
    doNotOptimizeAway(to<std::string>(
                          i32s[0],
                          ',',
                          u32s[0],
                          ',',
                          i32s[1],
                          ',',
                          u32s[1],
                          ',',
                          i32s[2],
                          ',',
                          u32s[2])
                          .size());
  }
}

BENCHMARK(preallocateTestInt64, n) {
  for (size_t i = 0; i < n; ++i) {
    doNotOptimizeAway(to<std::string>(
                          i64s[0],
                          ',',
                          u64s[0],
                          ',',
                          i64s[1],
                          ',',
                          u64s[1],
                          ',',
                          i64s[2],
                          ',',
                          u64s[2])
                          .size());
  }
}

#if FOLLY_HAVE_INT128_T
namespace {

__int128 i128s[] = {
    -(static_cast<__int128>(1) << 2),
    static_cast<__int128>(1) << 100,
    -(static_cast<__int128>(1) << 126),
};

unsigned __int128 u128s[] = {
    static_cast<unsigned __int128>(1) << 2,
    static_cast<unsigned __int128>(1) << 100,
    static_cast<unsigned __int128>(1) << 127,
};
} // namespace

BENCHMARK(preallocateTestInt128, n) {
  for (size_t i = 0; i < n; ++i) {
    doNotOptimizeAway(to<std::string>(
                          i128s[0],
                          ',',
                          u128s[0],
                          ',',
                          i128s[1],
                          ',',
                          u128s[1],
                          ',',
                          i128s[2],
                          ',',
                          u128s[2])
                          .size());
  }
}

BENCHMARK(preallocateTestNoFloatWithInt128, n) {
  for (size_t i = 0; i < n; ++i) {
    doNotOptimizeAway(
        to<std::string>(bigInt, someString, stdString, otherString).size());
    doNotOptimizeAway(
        to<std::string>(reallyShort, u128s[0], smallInt, i128s[2]).size());
    doNotOptimizeAway(
        to<std::string>(bigInt, i128s[0], stdString, u128s[1]).size());
    doNotOptimizeAway(
        to<std::string>(bigInt, stdString, dValue, otherString).size());
    doNotOptimizeAway(
        to<std::string>(bigInt, u128s[2], someString, reallyShort).size());
  }
}
#endif

BENCHMARK_DRAW_LINE();

static const StringIdenticalToBM<std::string> stringIdenticalToBM;
static const StringVariadicToBM<std::string> stringVariadicToBM;
static const StringIdenticalToBM<fbstring> fbstringIdenticalToBM;
static const StringVariadicToBM<fbstring> fbstringVariadicToBM;

#define DEFINE_BENCHMARK_GROUP(n)                \
  BENCHMARK_PARAM(u64ToAsciiClassicBM, n)        \
  BENCHMARK_RELATIVE_PARAM(u64ToAsciiTableBM, n) \
  BENCHMARK_RELATIVE_PARAM(u64ToAsciiFollyBM, n) \
  BENCHMARK_DRAW_LINE()

DEFINE_BENCHMARK_GROUP(1);
DEFINE_BENCHMARK_GROUP(2);
DEFINE_BENCHMARK_GROUP(3);
DEFINE_BENCHMARK_GROUP(4);
DEFINE_BENCHMARK_GROUP(5);
DEFINE_BENCHMARK_GROUP(6);
DEFINE_BENCHMARK_GROUP(7);
DEFINE_BENCHMARK_GROUP(8);
DEFINE_BENCHMARK_GROUP(9);
DEFINE_BENCHMARK_GROUP(10);
DEFINE_BENCHMARK_GROUP(11);
DEFINE_BENCHMARK_GROUP(12);
DEFINE_BENCHMARK_GROUP(13);
DEFINE_BENCHMARK_GROUP(14);
DEFINE_BENCHMARK_GROUP(15);
DEFINE_BENCHMARK_GROUP(16);
DEFINE_BENCHMARK_GROUP(17);
DEFINE_BENCHMARK_GROUP(18);
DEFINE_BENCHMARK_GROUP(19);
DEFINE_BENCHMARK_GROUP(20);

#undef DEFINE_BENCHMARK_GROUP

#define DEFINE_BENCHMARK_GROUP(n)                         \
  BENCHMARK_PARAM(u64ToStringClibMeasure, n)              \
  BENCHMARK_RELATIVE_PARAM(u64ToStringFollyMeasure, n)    \
  BENCHMARK_RELATIVE_PARAM(i64ToStringFollyMeasurePos, n) \
  BENCHMARK_RELATIVE_PARAM(i64ToStringFollyMeasureNeg, n) \
  BENCHMARK_DRAW_LINE()

DEFINE_BENCHMARK_GROUP(1);
DEFINE_BENCHMARK_GROUP(2);
DEFINE_BENCHMARK_GROUP(3);
DEFINE_BENCHMARK_GROUP(4);
DEFINE_BENCHMARK_GROUP(5);
DEFINE_BENCHMARK_GROUP(6);
DEFINE_BENCHMARK_GROUP(7);
DEFINE_BENCHMARK_GROUP(8);
DEFINE_BENCHMARK_GROUP(9);
DEFINE_BENCHMARK_GROUP(10);
DEFINE_BENCHMARK_GROUP(11);
DEFINE_BENCHMARK_GROUP(12);
DEFINE_BENCHMARK_GROUP(13);
DEFINE_BENCHMARK_GROUP(14);
DEFINE_BENCHMARK_GROUP(15);
DEFINE_BENCHMARK_GROUP(16);
DEFINE_BENCHMARK_GROUP(17);
DEFINE_BENCHMARK_GROUP(18);
DEFINE_BENCHMARK_GROUP(19);

// Only for u64
BENCHMARK_PARAM(u64ToStringClibMeasure, 20)
BENCHMARK_RELATIVE_PARAM(u64ToStringFollyMeasure, 20)
BENCHMARK_DRAW_LINE();

#undef DEFINE_BENCHMARK_GROUP

#if FOLLY_HAVE_INT128_T

void u128ToStringFollyMeasure(unsigned int n, size_t index) {
  checkArrayIndex(uint128Num, index);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(to<std::string>(uint128Num[index] + (i % 8)).size());
  }
}

void i128ToStringFollyMeasurePos(unsigned int n, size_t index) {
  checkArrayIndex(int128Pos, index);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(to<std::string>(int128Pos[index] + (i % 8)).size());
  }
}

void i128ToStringFollyMeasureNeg(unsigned int n, size_t index) {
  checkArrayIndex(int128Neg, index);
  FOR_EACH_RANGE (i, 0, n) {
    doNotOptimizeAway(to<std::string>(int128Neg[index] + (i % 8)).size());
  }
}

#define DEFINE_BENCHMARK_GROUP(n)                          \
  BENCHMARK_PARAM(u128ToStringFollyMeasure, n)             \
  BENCHMARK_RELATIVE_PARAM(i128ToStringFollyMeasurePos, n) \
  BENCHMARK_RELATIVE_PARAM(i128ToStringFollyMeasureNeg, n) \
  BENCHMARK_DRAW_LINE()

DEFINE_BENCHMARK_GROUP(1);
DEFINE_BENCHMARK_GROUP(2);
DEFINE_BENCHMARK_GROUP(3);
DEFINE_BENCHMARK_GROUP(4);
DEFINE_BENCHMARK_GROUP(5);
DEFINE_BENCHMARK_GROUP(6);
DEFINE_BENCHMARK_GROUP(7);
DEFINE_BENCHMARK_GROUP(8);
DEFINE_BENCHMARK_GROUP(9);
DEFINE_BENCHMARK_GROUP(10);
DEFINE_BENCHMARK_GROUP(11);
DEFINE_BENCHMARK_GROUP(12);
DEFINE_BENCHMARK_GROUP(13);
DEFINE_BENCHMARK_GROUP(14);
DEFINE_BENCHMARK_GROUP(15);
DEFINE_BENCHMARK_GROUP(16);
DEFINE_BENCHMARK_GROUP(17);
DEFINE_BENCHMARK_GROUP(18);
DEFINE_BENCHMARK_GROUP(19);
DEFINE_BENCHMARK_GROUP(20);
DEFINE_BENCHMARK_GROUP(21);
DEFINE_BENCHMARK_GROUP(22);
DEFINE_BENCHMARK_GROUP(23);
DEFINE_BENCHMARK_GROUP(24);
DEFINE_BENCHMARK_GROUP(25);
DEFINE_BENCHMARK_GROUP(26);
DEFINE_BENCHMARK_GROUP(27);
DEFINE_BENCHMARK_GROUP(28);
DEFINE_BENCHMARK_GROUP(29);
DEFINE_BENCHMARK_GROUP(30);
DEFINE_BENCHMARK_GROUP(31);
DEFINE_BENCHMARK_GROUP(32);
DEFINE_BENCHMARK_GROUP(33);
DEFINE_BENCHMARK_GROUP(34);
DEFINE_BENCHMARK_GROUP(35);
DEFINE_BENCHMARK_GROUP(36);
DEFINE_BENCHMARK_GROUP(37);
DEFINE_BENCHMARK_GROUP(38);
DEFINE_BENCHMARK_GROUP(39);

BENCHMARK_DRAW_LINE();

#undef DEFINE_BENCHMARK_GROUP

#endif

#define DEFINE_BENCHMARK_GROUP(n)                     \
  BENCHMARK_PARAM(clibAtoiMeasure, n)                 \
  BENCHMARK_RELATIVE_PARAM(lexicalCastMeasure, n)     \
  BENCHMARK_RELATIVE_PARAM(handwrittenAtoiMeasure, n) \
  BENCHMARK_RELATIVE_PARAM(follyAtoiMeasure, n)       \
  BENCHMARK_DRAW_LINE()

DEFINE_BENCHMARK_GROUP(1);
DEFINE_BENCHMARK_GROUP(2);
DEFINE_BENCHMARK_GROUP(3);
DEFINE_BENCHMARK_GROUP(4);
DEFINE_BENCHMARK_GROUP(5);
DEFINE_BENCHMARK_GROUP(6);
DEFINE_BENCHMARK_GROUP(7);
DEFINE_BENCHMARK_GROUP(8);
DEFINE_BENCHMARK_GROUP(9);
DEFINE_BENCHMARK_GROUP(10);
DEFINE_BENCHMARK_GROUP(11);
DEFINE_BENCHMARK_GROUP(12);
DEFINE_BENCHMARK_GROUP(13);
DEFINE_BENCHMARK_GROUP(14);
DEFINE_BENCHMARK_GROUP(15);
DEFINE_BENCHMARK_GROUP(16);
DEFINE_BENCHMARK_GROUP(17);
DEFINE_BENCHMARK_GROUP(18);
DEFINE_BENCHMARK_GROUP(19);

#undef DEFINE_BENCHMARK_GROUP

#define DEFINE_BENCHMARK_GROUP(T, n)            \
  BENCHMARK_PARAM(T##VariadicToBM, n)           \
  BENCHMARK_RELATIVE_PARAM(T##IdenticalToBM, n) \
  BENCHMARK_DRAW_LINE()

DEFINE_BENCHMARK_GROUP(string, 32);
DEFINE_BENCHMARK_GROUP(string, 1024);
DEFINE_BENCHMARK_GROUP(string, 32768);
DEFINE_BENCHMARK_GROUP(fbstring, 32);
DEFINE_BENCHMARK_GROUP(fbstring, 1024);
DEFINE_BENCHMARK_GROUP(fbstring, 32768);

#undef DEFINE_BENCHMARK_GROUP

namespace {

template <typename T>
inline void stringToTypeClassic(const char* str, uint32_t n) {
  for (uint32_t i = 0; i < n; ++i) {
    try {
      auto val = to<T>(str);
      doNotOptimizeAway(val);
    } catch (const std::exception& e) {
      doNotOptimizeAway(e.what());
    }
    doNotOptimizeAway(i);
  }
}

template <typename T>
inline void stringToTypeOptional(const char* str, uint32_t n) {
  for (uint32_t i = 0; i < n; ++i) {
    auto val = tryTo<T>(str);
    if (val.hasValue()) {
      doNotOptimizeAway(val.value());
    }
  }
}

template <typename T>
inline void ptrPairToIntClassic(StringPiece sp, uint32_t n) {
  for (uint32_t i = 0; i < n; ++i) {
    try {
      auto val = to<T>(sp.begin(), sp.end());
      doNotOptimizeAway(val);
    } catch (const std::exception& e) {
      doNotOptimizeAway(e.what());
    }
    doNotOptimizeAway(i);
  }
}

template <typename T>
inline void ptrPairToIntOptional(StringPiece sp, uint32_t n) {
  for (uint32_t i = 0; i < n; ++i) {
    auto val = tryTo<T>(sp.begin(), sp.end());
    if (val.hasValue()) {
      doNotOptimizeAway(val.value());
    }
  }
}

constexpr uint32_t kArithNumIter = 10000;

template <typename T, typename U>
inline size_t arithToArithClassic(const U* in, uint32_t numItems) {
  for (uint32_t i = 0; i < kArithNumIter; ++i) {
    for (uint32_t j = 0; j < numItems; ++j) {
      try {
        auto val = to<T>(in[j]);
        doNotOptimizeAway(val);
      } catch (const std::exception& e) {
        doNotOptimizeAway(e.what());
      }
      doNotOptimizeAway(j);
    }
    doNotOptimizeAway(i);
  }

  return kArithNumIter * numItems;
}

template <typename T, typename U>
inline size_t arithToArithOptional(const U* in, uint32_t numItems) {
  for (uint32_t i = 0; i < kArithNumIter; ++i) {
    for (uint32_t j = 0; j < numItems; ++j) {
      auto val = tryTo<T>(*in);
      doNotOptimizeAway(val.hasValue());
      if (val.hasValue()) {
        auto v2 = val.value();
        doNotOptimizeAway(v2);
      }
      doNotOptimizeAway(j);
    }
    doNotOptimizeAway(i);
  }

  return kArithNumIter * numItems;
}

} // namespace

namespace folly {
namespace conv_bench_detail {

// Keep this data global and non-const, so the compiler cannot make
// any assumptions about the actual values at compile time

std::array<int, 4> int2ScharGood{{-128, 127, 0, -50}};
std::array<int, 4> int2ScharBad{{-129, 128, 255, 10000}};
std::array<int, 4> int2UcharGood{{0, 1, 254, 255}};
std::array<int, 4> int2UcharBad{{-128, -1000, 256, -1}};

std::array<long long, 4> ll2SintOrFloatGood{{-2, -1, 0, 1}};
std::array<long long, 4> ll2SintOrFloatBad{{
    std::numeric_limits<long long>::min() / 5,
    std::numeric_limits<long long>::min() / 2,
    std::numeric_limits<long long>::max() / 2,
    std::numeric_limits<long long>::max() / 5,
}};
std::array<long long, 4> ll2UintGood{{1, 2, 3, 4}};
std::array<long long, 4> ll2UintBad{{-1, -2, -3, -4}};

std::array<double, 4> double2FloatGood{{1.0, 1.25, 2.5, 1000.0}};
std::array<double, 4> double2FloatBad{{1e100, 1e101, 1e102, 1e103}};
std::array<double, 4> double2IntGood{{1.0, 10.0, 100.0, 1000.0}};
std::array<double, 4> double2IntBad{{1e100, 1.25, 2.5, 100.00001}};
} // namespace conv_bench_detail
} // namespace folly

#define STRING_TO_TYPE_BENCHMARK(type, name, pass, fail) \
  BENCHMARK(stringTo##name##Classic, n) {                \
    stringToTypeClassic<type>(pass, n);                  \
  }                                                      \
  BENCHMARK(stringTo##name##ClassicError, n) {           \
    stringToTypeClassic<type>(fail, n);                  \
  }                                                      \
  BENCHMARK(stringTo##name##Optional, n) {               \
    stringToTypeOptional<type>(pass, n);                 \
  }                                                      \
  BENCHMARK(stringTo##name##OptionalError, n) {          \
    stringToTypeOptional<type>(fail, n);                 \
  }

#define PTR_PAIR_TO_INT_BENCHMARK(type, name, pass, fail) \
  BENCHMARK(ptrPairTo##name##Classic, n) {                \
    ptrPairToIntClassic<type>(pass, n);                   \
  }                                                       \
  BENCHMARK(ptrPairTo##name##ClassicError, n) {           \
    ptrPairToIntClassic<type>(fail, n);                   \
  }                                                       \
  BENCHMARK(ptrPairTo##name##Optional, n) {               \
    ptrPairToIntOptional<type>(pass, n);                  \
  }                                                       \
  BENCHMARK(ptrPairTo##name##OptionalError, n) {          \
    ptrPairToIntOptional<type>(fail, n);                  \
  }

#define ARITH_TO_ARITH_BENCHMARK(type, name, pass, fail)         \
  BENCHMARK_MULTI(name##Classic) {                               \
    return arithToArithClassic<type>(pass.data(), pass.size());  \
  }                                                              \
  BENCHMARK_MULTI(name##ClassicError) {                          \
    return arithToArithClassic<type>(fail.data(), fail.size());  \
  }                                                              \
  BENCHMARK_MULTI(name##Optional) {                              \
    return arithToArithOptional<type>(pass.data(), pass.size()); \
  }                                                              \
  BENCHMARK_MULTI(name##OptionalError) {                         \
    return arithToArithOptional<type>(fail.data(), fail.size()); \
  }

#define INT_TO_ARITH_BENCHMARK(type, name, pass, fail) \
  ARITH_TO_ARITH_BENCHMARK(type, intTo##name, pass, fail)

#define FLOAT_TO_ARITH_BENCHMARK(type, name, pass, fail) \
  ARITH_TO_ARITH_BENCHMARK(type, floatTo##name, pass, fail)

STRING_TO_TYPE_BENCHMARK(bool, BoolNum, " 1 ", "2")
STRING_TO_TYPE_BENCHMARK(bool, BoolStr, "true", "xxxx")
BENCHMARK_DRAW_LINE();
STRING_TO_TYPE_BENCHMARK(float, FloatNum, " 3.14 ", "3e5000x")
STRING_TO_TYPE_BENCHMARK(float, FloatStr, "-infinity", "xxxx")
STRING_TO_TYPE_BENCHMARK(double, DoubleNum, " 3.14 ", "3e5000x")
STRING_TO_TYPE_BENCHMARK(double, DoubleStr, "-infinity", "xxxx")
BENCHMARK_DRAW_LINE();
STRING_TO_TYPE_BENCHMARK(signed char, CharSigned, " -47 ", "1000")
STRING_TO_TYPE_BENCHMARK(unsigned char, CharUnsigned, " 47 ", "-47")
STRING_TO_TYPE_BENCHMARK(int, IntSigned, " -4711 ", "-10000000000000000000000")
STRING_TO_TYPE_BENCHMARK(unsigned int, IntUnsigned, " 4711 ", "-4711")
STRING_TO_TYPE_BENCHMARK(
    long long,
    LongLongSigned,
    " -8123456789123456789 ",
    "-10000000000000000000000")
STRING_TO_TYPE_BENCHMARK(
    unsigned long long, LongLongUnsigned, " 18123456789123456789 ", "-4711")
BENCHMARK_DRAW_LINE();

PTR_PAIR_TO_INT_BENCHMARK(signed char, CharSigned, "-47", "1000")
PTR_PAIR_TO_INT_BENCHMARK(unsigned char, CharUnsigned, "47", "1000")
PTR_PAIR_TO_INT_BENCHMARK(int, IntSigned, "-4711", "-10000000000000000000000")
PTR_PAIR_TO_INT_BENCHMARK(
    unsigned int, IntUnsigned, "4711", "10000000000000000000000")
PTR_PAIR_TO_INT_BENCHMARK(
    long long,
    LongLongSigned,
    "-8123456789123456789",
    "-10000000000000000000000")
PTR_PAIR_TO_INT_BENCHMARK(
    unsigned long long,
    LongLongUnsigned,
    "18123456789123456789",
    "20000000000000000000")
BENCHMARK_DRAW_LINE();

INT_TO_ARITH_BENCHMARK(signed char, CharSigned, int2ScharGood, int2ScharBad)
INT_TO_ARITH_BENCHMARK(unsigned char, CharUnsigned, int2UcharGood, int2UcharBad)
INT_TO_ARITH_BENCHMARK(int, IntSigned, ll2SintOrFloatGood, ll2SintOrFloatBad)
INT_TO_ARITH_BENCHMARK(unsigned int, IntUnsigned, ll2UintGood, ll2UintBad)
BENCHMARK_DRAW_LINE();
INT_TO_ARITH_BENCHMARK(float, Float, ll2SintOrFloatGood, ll2SintOrFloatBad)
BENCHMARK_DRAW_LINE();
FLOAT_TO_ARITH_BENCHMARK(float, Float, double2FloatGood, double2FloatBad)
BENCHMARK_DRAW_LINE();
FLOAT_TO_ARITH_BENCHMARK(int, Int, double2IntGood, double2IntBad)

#undef STRING_TO_TYPE_BENCHMARK
#undef PTR_PAIR_TO_INT_BENCHMARK
#undef ARITH_TO_ARITH_BENCHMARK
#undef INT_TO_ARITH_BENCHMARK
#undef FLOAT_TO_ARITH_BENCHMARK

int main(int argc, char** argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, true);
  folly::runBenchmarks();
  return 0;
}