Skip to content

Commit

Permalink
Improve dump_integer performance by implementing a more efficient int…
Browse files Browse the repository at this point in the history
…2ascii

This commits implements a faster int2ascii inspired by "Fastware" talk given
by Andrei Alexandrescu.
See: https://www.youtube.com/watch?v=o4-CwDo2zpg
  • Loading branch information
nickaein committed Jan 6, 2019
1 parent 676c847 commit 7a6102f
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 28 deletions.
105 changes: 91 additions & 14 deletions include/nlohmann/detail/output/serializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,40 @@ class serializer
}
}

/*!
@brief count digits
Count the number of decimal (base 10) digits for an input unsigned integer.
@param[in] x unsigned integer number to count its digits
@return number of decimal digits
*/
inline unsigned int count_digits(number_unsigned_t x)
{
unsigned int n_digits = 1;
for (;;)
{
if (x < 10)
{
return n_digits;
}
if (x < 100)
{
return n_digits + 1;
}
if (x < 1000)
{
return n_digits + 2;
}
if (x < 10000)
{
return n_digits + 3;
}
x = x / 10000u;
n_digits += 4;
}
}

/*!
@brief dump an integer
Expand All @@ -542,35 +576,78 @@ class serializer
int> = 0>
void dump_integer(NumberType x)
{
static constexpr std::array<std::array<char, 3>, 100> digits_to_99
{
{
{'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'},
{'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'},
{'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
{'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'},
{'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'},
{'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
{'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'},
{'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'},
{'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
{'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'},
}
};

// special case for "0"
if (x == 0)
{
o->write_character('0');
return;
}

const bool is_negative = std::is_same<NumberType, number_integer_t>::value and not (x >= 0); // see issue #755
std::size_t i = 0;
// use a pointer to fill the buffer
auto buffer_ptr = begin(number_buffer);

const bool is_negative = std::is_same<NumberType, number_integer_t>::value and not(x >= 0); // see issue #755
number_unsigned_t abs_value;

while (x != 0)
if (is_negative)
{
*buffer_ptr++ = '-';
abs_value = static_cast<number_unsigned_t>(0 - x);
}
else
{
// spare 1 byte for '\0'
assert(i < number_buffer.size() - 1);
abs_value = static_cast<number_unsigned_t>(x);
}

auto n_digits = count_digits(abs_value);

// spare 1 byte for '\0' and 1 byte for
// possible minus sign
assert(n_digits < number_buffer.size() - 2);

const auto digit = std::labs(static_cast<long>(x % 10));
number_buffer[i++] = static_cast<char>('0' + digit);
x /= 10;
// jump to the end to generate the string from backward
// so we later avoid reversing the result
buffer_ptr += n_digits;

// Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu
// See: https://www.youtube.com/watch?v=o4-CwDo2zpg
auto buffer_end = buffer_ptr;
while (abs_value >= 100)
{
auto digits_index = static_cast<unsigned>((abs_value % 100));
abs_value /= 100;
*--buffer_ptr = digits_to_99[digits_index][1];
*--buffer_ptr = digits_to_99[digits_index][0];
}

if (is_negative)
if (abs_value >= 10)
{
unsigned digits_index = static_cast<unsigned>(abs_value);
*--buffer_ptr = digits_to_99[digits_index][1];
*--buffer_ptr = digits_to_99[digits_index][0];
}
else
{
// make sure there is capacity for the '-'
assert(i < number_buffer.size() - 2);
number_buffer[i++] = '-';
*--buffer_ptr = static_cast<char>('0' + abs_value);
}

std::reverse(number_buffer.begin(), number_buffer.begin() + i);
o->write_characters(number_buffer.data(), i);
o->write_characters(number_buffer.data(), static_cast<size_t>(buffer_end - begin(number_buffer)));
}

/*!
Expand Down
105 changes: 91 additions & 14 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11410,6 +11410,40 @@ class serializer
}
}

/*!
@brief count digits

Count the number of decimal (base 10) digits for an input unsigned integer.

@param[in] x unsigned integer number to count its digits
@return number of decimal digits
*/
inline unsigned int count_digits(number_unsigned_t x)
{
unsigned int n_digits = 1;
for (;;)
{
if (x < 10)
{
return n_digits;
}
if (x < 100)
{
return n_digits + 1;
}
if (x < 1000)
{
return n_digits + 2;
}
if (x < 10000)
{
return n_digits + 3;
}
x = x / 10000u;
n_digits += 4;
}
}

/*!
@brief dump an integer

Expand All @@ -11425,35 +11459,78 @@ class serializer
int> = 0>
void dump_integer(NumberType x)
{
static constexpr std::array<std::array<char, 3>, 100> digits_to_99
{
{
{'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'},
{'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'},
{'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
{'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'},
{'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'},
{'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
{'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'},
{'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'},
{'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
{'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'},
}
};

// special case for "0"
if (x == 0)
{
o->write_character('0');
return;
}

const bool is_negative = std::is_same<NumberType, number_integer_t>::value and not (x >= 0); // see issue #755
std::size_t i = 0;
// use a pointer to fill the buffer
auto buffer_ptr = begin(number_buffer);

const bool is_negative = std::is_same<NumberType, number_integer_t>::value and not(x >= 0); // see issue #755
number_unsigned_t abs_value;

while (x != 0)
if (is_negative)
{
*buffer_ptr++ = '-';
abs_value = static_cast<number_unsigned_t>(0 - x);
}
else
{
// spare 1 byte for '\0'
assert(i < number_buffer.size() - 1);
abs_value = static_cast<number_unsigned_t>(x);
}

auto n_digits = count_digits(abs_value);

// spare 1 byte for '\0' and 1 byte for
// possible minus sign
assert(n_digits < number_buffer.size() - 2);

const auto digit = std::labs(static_cast<long>(x % 10));
number_buffer[i++] = static_cast<char>('0' + digit);
x /= 10;
// jump to the end to generate the string from backward
// so we later avoid reversing the result
buffer_ptr += n_digits;

// Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu
// See: https://www.youtube.com/watch?v=o4-CwDo2zpg
auto buffer_end = buffer_ptr;
while (abs_value >= 100)
{
auto digits_index = static_cast<unsigned>((abs_value % 100));
abs_value /= 100;
*--buffer_ptr = digits_to_99[digits_index][1];
*--buffer_ptr = digits_to_99[digits_index][0];
}

if (is_negative)
if (abs_value >= 10)
{
unsigned digits_index = static_cast<unsigned>(abs_value);
*--buffer_ptr = digits_to_99[digits_index][1];
*--buffer_ptr = digits_to_99[digits_index][0];
}
else
{
// make sure there is capacity for the '-'
assert(i < number_buffer.size() - 2);
number_buffer[i++] = '-';
*--buffer_ptr = static_cast<char>('0' + abs_value);
}

std::reverse(number_buffer.begin(), number_buffer.begin() + i);
o->write_characters(number_buffer.data(), i);
o->write_characters(number_buffer.data(), static_cast<size_t>(buffer_end - begin(number_buffer)));
}

/*!
Expand Down

0 comments on commit 7a6102f

Please sign in to comment.