Commit c8613fb4 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook GitHub Bot

migrate from uint64ToBufferUnsafe

Summary: Migrate the codebase away from the ad-hoc `folly::uint64ToBufferUnsafe` and to `folly::to_ascii_decimal` which is intended for these cases.

Reviewed By: WillerZ

Differential Revision: D27281875

fbshipit-source-id: 0c98749e4aed9c873853eed2221cf54a89279ff4
parent b91dd2f6
......@@ -123,6 +123,7 @@
#include <folly/Utility.h>
#include <folly/lang/Exception.h>
#include <folly/lang/Pretty.h>
#include <folly/lang/ToAscii.h>
#include <folly/portability/Math.h>
namespace folly {
......@@ -366,120 +367,6 @@ inline size_t unsafeTelescope128(
} // namespace detail
#endif
/**
* Returns the number of digits in the base 10 representation of an
* uint64_t. Useful for preallocating buffers and such. It's also used
* internally, see below. Measurements suggest that defining a
* separate overload for 32-bit integers is not worthwhile.
*/
inline uint32_t digits10(uint64_t v) {
#ifdef __x86_64__
// For this arch we can get a little help from specialized CPU instructions
// which can count leading zeroes; 64 minus that is appx. log (base 2).
// Use that to approximate base-10 digits (log_10) and then adjust if needed.
// 10^i, defined for i 0 through 19.
// This is 20 * 8 == 160 bytes, which fits neatly into 5 cache lines
// (assuming a cache line size of 64).
alignas(64) static const uint64_t powersOf10[20] = {
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000,
100000000000,
1000000000000,
10000000000000,
100000000000000,
1000000000000000,
10000000000000000,
100000000000000000,
1000000000000000000,
10000000000000000000UL,
};
// "count leading zeroes" operation not valid; for 0; special case this.
if (UNLIKELY(!v)) {
return 1;
}
// bits is in the ballpark of log_2(v).
const uint32_t leadingZeroes = __builtin_clzll(v);
const auto bits = 63 - leadingZeroes;
// approximate log_10(v) == log_10(2) * bits.
// Integer magic below: 77/256 is appx. 0.3010 (log_10(2)).
// The +1 is to make this the ceiling of the log_10 estimate.
const uint32_t minLength = 1 + ((bits * 77) >> 8);
// return that log_10 lower bound, plus adjust if input >= 10^(that bound)
// in case there's a small error and we misjudged length.
return minLength + uint32_t(v >= powersOf10[minLength]);
#else
uint32_t result = 1;
while (true) {
if (LIKELY(v < 10)) {
return result;
}
if (LIKELY(v < 100)) {
return result + 1;
}
if (LIKELY(v < 1000)) {
return result + 2;
}
if (LIKELY(v < 10000)) {
return result + 3;
}
// Skip ahead by 4 orders of magnitude
v /= 10000U;
result += 4;
}
#endif
}
/**
* Copies the ASCII base 10 representation of v into buffer and
* returns the number of bytes written. Does NOT append a \0. Assumes
* the buffer points to digits10(v) bytes of valid memory. Note that
* uint64_t needs at most 20 bytes, uint32_t needs at most 10 bytes,
* uint16_t needs at most 5 bytes, and so on. Measurements suggest
* that defining a separate overload for 32-bit integers is not
* worthwhile.
*
* This primitive is unsafe because it makes the size assumption and
* because it does not add a terminating \0.
*/
inline uint32_t uint64ToBufferUnsafe(uint64_t v, char* const buffer) {
auto const result = digits10(v);
// WARNING: using size_t or pointer arithmetic for pos slows down
// the loop below 20x. This is because several 32-bit ops can be
// done in parallel, but only fewer 64-bit ones.
uint32_t pos = result - 1;
while (v >= 10) {
// Keep these together so a peephole optimization "sees" them and
// computes them in one shot.
auto const q = v / 10;
auto const r = v % 10;
buffer[pos--] = static_cast<char>('0' + r);
v = q;
}
// Last digit is trivial to handle
buffer[pos] = static_cast<char>(v + '0');
return result;
}
/**
* A single char gets appended.
*/
......@@ -645,15 +532,13 @@ typename std::enable_if<
std::is_integral<Src>::value && std::is_signed<Src>::value &&
IsSomeString<Tgt>::value && sizeof(Src) >= 4>::type
toAppend(Src value, Tgt* result) {
char buffer[20];
char buffer[to_ascii_size_max_decimal<uint64_t>];
auto uvalue = value < 0 ? ~static_cast<uint64_t>(value) + 1
: static_cast<uint64_t>(value);
if (value < 0) {
result->push_back('-');
result->append(
buffer,
uint64ToBufferUnsafe(~static_cast<uint64_t>(value) + 1, buffer));
} else {
result->append(buffer, uint64ToBufferUnsafe(uint64_t(value), buffer));
}
result->append(buffer, to_ascii_decimal(buffer, uvalue));
}
template <class Src>
......@@ -662,14 +547,9 @@ typename std::enable_if<
sizeof(Src) >= 4 && sizeof(Src) < 16,
size_t>::type
estimateSpaceNeeded(Src value) {
if (value < 0) {
// When "value" is the smallest negative, negating it would evoke
// undefined behavior, so, instead of writing "-value" below, we write
// "~static_cast<uint64_t>(value) + 1"
return 1 + digits10(~static_cast<uint64_t>(value) + 1);
}
return digits10(static_cast<uint64_t>(value));
auto uvalue = value < 0 ? ~static_cast<uint64_t>(value) + 1
: static_cast<uint64_t>(value);
return size_t(value < 0) + to_ascii_size_decimal(uvalue);
}
/**
......@@ -680,8 +560,8 @@ typename std::enable_if<
std::is_integral<Src>::value && !std::is_signed<Src>::value &&
IsSomeString<Tgt>::value && sizeof(Src) >= 4>::type
toAppend(Src value, Tgt* result) {
char buffer[20];
result->append(buffer, uint64ToBufferUnsafe(value, buffer));
char buffer[to_ascii_size_max_decimal<uint64_t>];
result->append(buffer, to_ascii_decimal(buffer, value));
}
template <class Src>
......@@ -690,7 +570,7 @@ typename std::enable_if<
sizeof(Src) >= 4 && sizeof(Src) < 16,
size_t>::type
estimateSpaceNeeded(Src value) {
return digits10(value);
return to_ascii_size_decimal(value);
}
/**
......
......@@ -30,6 +30,7 @@
#include <folly/MapUtil.h>
#include <folly/Traits.h>
#include <folly/lang/Exception.h>
#include <folly/lang/ToAscii.h>
#include <folly/portability/Windows.h>
// Ignore -Wformat-nonliteral and -Wconversion warnings within this file
......@@ -521,8 +522,9 @@ class FormatValue<
"' specifier");
valBufBegin = valBuf + 1; // room for sign
// Use uintToBuffer, faster than sprintf
valBufEnd = valBufBegin + uint64ToBufferUnsafe(uval, valBufBegin);
// Use to_ascii_decimal, faster than sprintf
valBufEnd = valBufBegin +
to_ascii_decimal(valBufBegin, valBuf + sizeof(valBuf), uval);
if (arg.thousandsSeparator) {
detail::insertThousandsGroupingUnsafe(valBufBegin, &valBufEnd);
}
......
......@@ -25,6 +25,7 @@
#include <folly/Benchmark.h>
#include <folly/CppAttributes.h>
#include <folly/container/Foreach.h>
#include <folly/lang/ToAscii.h>
using namespace std;
using namespace folly;
......@@ -345,7 +346,7 @@ unsigned u64ToAsciiTable(uint64_t value, char* dst) {
"80818283848586878889"
"90919293949596979899";
uint32_t const length = digits10(value);
uint32_t const length = to_ascii_size_decimal(value);
uint32_t next = length - 1;
while (value >= 100) {
auto const i = (value % 100) * 2;
......@@ -407,7 +408,7 @@ void u64ToAsciiFollyBM(unsigned int n, size_t index) {
checkArrayIndex(uint64Num, index);
char buf[20];
FOR_EACH_RANGE (i, 0, n) {
doNotOptimizeAway(uint64ToBufferUnsafe(uint64Num[index] + (i % 8), buf));
doNotOptimizeAway(to_ascii_decimal(buf, uint64Num[index] + (i % 8)));
}
}
......@@ -465,7 +466,7 @@ void u2aAppendFollyBM(unsigned int n, size_t index) {
FOR_EACH_RANGE (i, 0, n) {
// auto buf = &s.back() + 1;
char buffer[20];
s.append(buffer, uint64ToBufferUnsafe(uint64Num[index] + (i % 8), buffer));
s.append(buffer, to_ascii_decimal(buffer, uint64Num[index] + (i % 8)));
doNotOptimizeAway(s.size());
}
}
......
......@@ -36,54 +36,6 @@
using namespace std;
using namespace folly;
TEST(Conv, digits10) {
char buffer[100];
uint64_t power;
// first, some basic sniff tests
EXPECT_EQ(1, digits10(0));
EXPECT_EQ(1, digits10(1));
EXPECT_EQ(1, digits10(9));
EXPECT_EQ(2, digits10(10));
EXPECT_EQ(2, digits10(99));
EXPECT_EQ(3, digits10(100));
EXPECT_EQ(3, digits10(999));
EXPECT_EQ(4, digits10(1000));
EXPECT_EQ(4, digits10(9999));
EXPECT_EQ(20, digits10(18446744073709551615ULL));
// try the first X nonnegatives.
// Covers some more cases of 2^p, 10^p
for (uint64_t i = 0; i < 100000; i++) {
snprintf(buffer, sizeof(buffer), "%" PRIu64, i);
EXPECT_EQ(strlen(buffer), digits10(i));
}
// try powers of 2
power = 1;
for (int p = 0; p < 64; p++) {
snprintf(buffer, sizeof(buffer), "%" PRIu64, power);
EXPECT_EQ(strlen(buffer), digits10(power));
snprintf(buffer, sizeof(buffer), "%" PRIu64, power - 1);
EXPECT_EQ(strlen(buffer), digits10(power - 1));
snprintf(buffer, sizeof(buffer), "%" PRIu64, power + 1);
EXPECT_EQ(strlen(buffer), digits10(power + 1));
power *= 2;
}
// try powers of 10
power = 1;
for (int p = 0; p < 20; p++) {
snprintf(buffer, sizeof(buffer), "%" PRIu64, power);
EXPECT_EQ(strlen(buffer), digits10(power));
snprintf(buffer, sizeof(buffer), "%" PRIu64, power - 1);
EXPECT_EQ(strlen(buffer), digits10(power - 1));
snprintf(buffer, sizeof(buffer), "%" PRIu64, power + 1);
EXPECT_EQ(strlen(buffer), digits10(power + 1));
power *= 10;
}
}
// Test to<T>(T)
TEST(Conv, Type2Type) {
bool boolV = true;
......@@ -1332,44 +1284,6 @@ TEST(Conv, TryPtrPairToInt) {
EXPECT_EQ(rv4.value(), 4711);
}
TEST(Conv, NewUint64ToString) {
char buf[21];
#define THE_GREAT_EXPECTATIONS(n, len) \
do { \
EXPECT_EQ((len), uint64ToBufferUnsafe((n), buf)); \
buf[(len)] = 0; \
auto s = string(#n); \
s = s.substr(0, s.size() - 2); \
EXPECT_EQ(s, buf); \
} while (0)
THE_GREAT_EXPECTATIONS(0UL, 1);
THE_GREAT_EXPECTATIONS(1UL, 1);
THE_GREAT_EXPECTATIONS(12UL, 2);
THE_GREAT_EXPECTATIONS(123UL, 3);
THE_GREAT_EXPECTATIONS(1234UL, 4);
THE_GREAT_EXPECTATIONS(12345UL, 5);
THE_GREAT_EXPECTATIONS(123456UL, 6);
THE_GREAT_EXPECTATIONS(1234567UL, 7);
THE_GREAT_EXPECTATIONS(12345678UL, 8);
THE_GREAT_EXPECTATIONS(123456789UL, 9);
THE_GREAT_EXPECTATIONS(1234567890UL, 10);
THE_GREAT_EXPECTATIONS(12345678901UL, 11);
THE_GREAT_EXPECTATIONS(123456789012UL, 12);
THE_GREAT_EXPECTATIONS(1234567890123UL, 13);
THE_GREAT_EXPECTATIONS(12345678901234UL, 14);
THE_GREAT_EXPECTATIONS(123456789012345UL, 15);
THE_GREAT_EXPECTATIONS(1234567890123456UL, 16);
THE_GREAT_EXPECTATIONS(12345678901234567UL, 17);
THE_GREAT_EXPECTATIONS(123456789012345678UL, 18);
THE_GREAT_EXPECTATIONS(1234567890123456789UL, 19);
THE_GREAT_EXPECTATIONS(18446744073709551614UL, 20);
THE_GREAT_EXPECTATIONS(18446744073709551615UL, 20);
#undef THE_GREAT_EXPECTATIONS
}
TEST(Conv, allocate_size) {
std::string str1 = "meh meh meh";
std::string str2 = "zdech zdech zdech";
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment