mirror of
https://github.com/fastfloat/fast_float.git
synced 2026-06-16 17:06:12 +08:00
Merge branch 'main' of https://github.com/fastfloat/fast_float
This commit is contained in:
commit
ac1e4dd805
@ -18,9 +18,9 @@ requires C++11):
|
||||
from_chars_result from_chars(char const *first, char const *last, float &value, ...);
|
||||
from_chars_result from_chars(char const *first, char const *last, double &value, ...);
|
||||
```
|
||||
If they are available on your system, we also support fixed-width floating-point types such as `std::float64_t`, `std::float32_t`, `std::float16_t`, and `std::bfloat16_t`.
|
||||
|
||||
You can also parse integer types:
|
||||
|
||||
You can also parse integer types such as `char`, `short`, `long`, `long long`, `unsigned char`, `unsigned short`, `unsigned long`, `unsigned long long`, `bool` (0/1), `int8_t`, `int16_t`, `int32_t`, `int64_t`, `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t`.
|
||||
```C++
|
||||
from_chars_result from_chars(char const *first, char const *last, int &value, ...);
|
||||
from_chars_result from_chars(char const *first, char const *last, unsigned &value, ...);
|
||||
|
||||
@ -1,9 +1,27 @@
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
counters
|
||||
GIT_REPOSITORY https://github.com/lemire/counters.git
|
||||
GIT_TAG v2.2.0
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(counters)
|
||||
|
||||
add_executable(realbenchmark benchmark.cpp)
|
||||
target_link_libraries(realbenchmark PRIVATE counters::counters)
|
||||
add_executable(bench_ip bench_ip.cpp)
|
||||
target_link_libraries(bench_ip PRIVATE counters::counters)
|
||||
|
||||
set_property(
|
||||
TARGET realbenchmark
|
||||
PROPERTY CXX_STANDARD 17)
|
||||
|
||||
set_property(
|
||||
TARGET bench_ip
|
||||
PROPERTY CXX_STANDARD 17)
|
||||
target_link_libraries(realbenchmark PUBLIC fast_float)
|
||||
target_link_libraries(bench_ip PUBLIC fast_float)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
# Define the external project
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
182
benchmarks/bench_ip.cpp
Normal file
182
benchmarks/bench_ip.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
#include "counters/bench.h"
|
||||
#include "fast_float/fast_float.h"
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
void pretty_print(size_t volume, size_t bytes, std::string name,
|
||||
counters::event_aggregate agg) {
|
||||
if (agg.inner_count > 1) {
|
||||
printf("# (inner count: %d)\n", agg.inner_count);
|
||||
}
|
||||
printf("%-40s : ", name.c_str());
|
||||
printf(" %5.2f GB/s ", bytes / agg.fastest_elapsed_ns());
|
||||
printf(" %5.1f Mip/s ", volume * 1000.0 / agg.fastest_elapsed_ns());
|
||||
printf(" %5.2f ns/ip ", agg.fastest_elapsed_ns() / volume);
|
||||
if (counters::event_collector().has_events()) {
|
||||
printf(" %5.2f GHz ", agg.fastest_cycles() / agg.fastest_elapsed_ns());
|
||||
printf(" %5.2f c/ip ", agg.fastest_cycles() / volume);
|
||||
printf(" %5.2f i/ip ", agg.fastest_instructions() / volume);
|
||||
printf(" %5.2f c/b ", agg.fastest_cycles() / bytes);
|
||||
printf(" %5.2f i/b ", agg.fastest_instructions() / bytes);
|
||||
printf(" %5.2f i/c ", agg.fastest_instructions() / agg.fastest_cycles());
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
fastfloat_really_inline const char *seek_ip_end(const char *p,
|
||||
const char *pend) {
|
||||
const char *current = p;
|
||||
size_t count = 0;
|
||||
for (; current != pend; ++current) {
|
||||
if (*current == '.') {
|
||||
count++;
|
||||
if (count == 3) {
|
||||
++current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (current != pend) {
|
||||
if (*current <= '9' && *current >= '0') {
|
||||
++current;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
enum class parse_method { standard, fast_float };
|
||||
|
||||
template <parse_method use_standard>
|
||||
fastfloat_really_inline std::pair<bool, uint32_t>
|
||||
simple_parse_ip_line(const char *p, const char *pend) {
|
||||
const char *current = p;
|
||||
uint32_t ip = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
uint8_t value;
|
||||
if constexpr (use_standard == parse_method::standard) {
|
||||
auto r = std::from_chars(current, pend, value);
|
||||
if (r.ec != std::errc()) {
|
||||
return {false, 0};
|
||||
}
|
||||
current = r.ptr;
|
||||
} else if constexpr (use_standard == parse_method::fast_float) {
|
||||
auto r = fast_float::from_chars(current, pend, value);
|
||||
if (r.ec != std::errc()) {
|
||||
return {false, 0};
|
||||
}
|
||||
current = r.ptr;
|
||||
}
|
||||
ip = (ip << 8) | value;
|
||||
if (i < 3) {
|
||||
if (current == pend || *current++ != '.') {
|
||||
return {false, 0};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {true, ip};
|
||||
}
|
||||
|
||||
static std::string make_ip_line(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
|
||||
std::string s;
|
||||
s.reserve(16);
|
||||
s += std::to_string(a);
|
||||
s += '.';
|
||||
s += std::to_string(b);
|
||||
s += '.';
|
||||
s += std::to_string(c);
|
||||
s += '.';
|
||||
s += std::to_string(d);
|
||||
s += '\n';
|
||||
return s;
|
||||
}
|
||||
|
||||
int main() {
|
||||
constexpr size_t N = 15000;
|
||||
std::mt19937 rng(1234);
|
||||
std::uniform_int_distribution<int> dist(0, 255);
|
||||
|
||||
std::string buf;
|
||||
constexpr size_t ip_size = 16;
|
||||
buf.reserve(N * ip_size);
|
||||
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
uint8_t a = (uint8_t)dist(rng);
|
||||
uint8_t b = (uint8_t)dist(rng);
|
||||
uint8_t c = (uint8_t)dist(rng);
|
||||
uint8_t d = (uint8_t)dist(rng);
|
||||
std::string ip_line = make_ip_line(a, b, c, d);
|
||||
ip_line.resize(ip_size, ' '); // pad to fixed size
|
||||
buf.append(ip_line);
|
||||
}
|
||||
|
||||
// sentinel to allow 4-byte loads at end
|
||||
buf.append(4, '\0');
|
||||
|
||||
const size_t bytes = buf.size() - 4; // exclude sentinel from throughput
|
||||
const size_t volume = N;
|
||||
|
||||
volatile uint32_t sink = 0;
|
||||
std::string buffer(ip_size * N, ' ');
|
||||
|
||||
pretty_print(volume, bytes, "memcpy baseline", counters::bench([&]() {
|
||||
std::memcpy((char *)buffer.data(), buf.data(), bytes);
|
||||
}));
|
||||
|
||||
pretty_print(volume, bytes, "just_seek_ip_end (no parse)",
|
||||
counters::bench([&]() {
|
||||
const char *p = buf.data();
|
||||
const char *pend = buf.data() + bytes;
|
||||
uint32_t sum = 0;
|
||||
int ok = 0;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
const char *q = seek_ip_end(p, pend);
|
||||
sum += (uint32_t)(q - p);
|
||||
p += ip_size;
|
||||
}
|
||||
sink += sum;
|
||||
}));
|
||||
|
||||
pretty_print(volume, bytes, "parse_ip_std_fromchars", counters::bench([&]() {
|
||||
const char *p = buf.data();
|
||||
const char *pend = buf.data() + bytes;
|
||||
uint32_t sum = 0;
|
||||
int ok = 0;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
auto [ok, ip] =
|
||||
simple_parse_ip_line<parse_method::standard>(p, pend);
|
||||
sum += ip;
|
||||
if (!ok) {
|
||||
std::abort();
|
||||
}
|
||||
p += ip_size;
|
||||
}
|
||||
sink += sum;
|
||||
}));
|
||||
|
||||
pretty_print(volume, bytes, "parse_ip_fastfloat", counters::bench([&]() {
|
||||
const char *p = buf.data();
|
||||
const char *pend = buf.data() + bytes;
|
||||
uint32_t sum = 0;
|
||||
int ok = 0;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
auto [ok, ip] =
|
||||
simple_parse_ip_line<parse_method::fast_float>(p, pend);
|
||||
sum += ip;
|
||||
if (!ok) {
|
||||
std::abort();
|
||||
}
|
||||
p += ip_size;
|
||||
}
|
||||
sink += sum;
|
||||
}));
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@ -6,7 +6,7 @@
|
||||
#if defined(__linux__) || (__APPLE__ && __aarch64__)
|
||||
#define USING_COUNTERS
|
||||
#endif
|
||||
#include "event_counter.h"
|
||||
#include "counters/event_counter.h"
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
@ -43,13 +43,13 @@ Value findmax_fastfloat(std::vector<std::basic_string<CharT>> &s) {
|
||||
|
||||
#ifdef USING_COUNTERS
|
||||
|
||||
event_collector collector{};
|
||||
counters::event_collector collector{};
|
||||
|
||||
template <class T, class CharT>
|
||||
std::vector<event_count>
|
||||
std::vector<counters::event_count>
|
||||
time_it_ns(std::vector<std::basic_string<CharT>> &lines, T const &function,
|
||||
size_t repeat) {
|
||||
std::vector<event_count> aggregate;
|
||||
std::vector<counters::event_count> aggregate;
|
||||
bool printed_bug = false;
|
||||
for (size_t i = 0; i != repeat; ++i) {
|
||||
|
||||
@ -66,7 +66,7 @@ time_it_ns(std::vector<std::basic_string<CharT>> &lines, T const &function,
|
||||
}
|
||||
|
||||
void pretty_print(size_t volume, size_t number_of_floats, std::string name,
|
||||
std::vector<event_count> events) {
|
||||
std::vector<counters::event_count> events) {
|
||||
double volumeMB = volume / (1024. * 1024.);
|
||||
double average_ns{0};
|
||||
double min_ns{DBL_MAX};
|
||||
@ -78,7 +78,7 @@ void pretty_print(size_t volume, size_t number_of_floats, std::string name,
|
||||
double branches_avg{0};
|
||||
double branch_misses_min{0};
|
||||
double branch_misses_avg{0};
|
||||
for (event_count e : events) {
|
||||
for (counters::event_count e : events) {
|
||||
double ns = e.elapsed_ns();
|
||||
average_ns += ns;
|
||||
min_ns = min_ns < ns ? min_ns : ns;
|
||||
@ -96,7 +96,7 @@ void pretty_print(size_t volume, size_t number_of_floats, std::string name,
|
||||
branches_avg += branches;
|
||||
branches_min = branches_min < branches ? branches_min : branches;
|
||||
|
||||
double branch_misses = e.missed_branches();
|
||||
double branch_misses = e.branch_misses();
|
||||
branch_misses_avg += branch_misses;
|
||||
branch_misses_min =
|
||||
branch_misses_min < branch_misses ? branch_misses_min : branch_misses;
|
||||
|
||||
@ -5,4 +5,8 @@ $CXX $CFLAGS $CXXFLAGS \
|
||||
-c $SRC/fast_float/fuzz/from_chars.cc -o from_chars.o
|
||||
|
||||
$CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE from_chars.o \
|
||||
-o $OUT/from_chars
|
||||
-o $OUT/from_chars
|
||||
|
||||
# Build unit tests
|
||||
cmake -DFASTFLOAT_TEST=ON -DCMAKE_EXE_LINKER_FLAGS="-lpthread"
|
||||
make
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#
|
||||
# Reference :
|
||||
# Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to appear)
|
||||
# Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback, Software: Practice and Experience 53 (6), 2023 https://arxiv.org/abs/2212.06644
|
||||
#
|
||||
|
||||
all_tqs = []
|
||||
@ -74,8 +74,8 @@ for j, tq in enumerate(all_tqs):
|
||||
for _, w in convergents(continued_fraction(tq, 2 ** 137)):
|
||||
if w >= 2 ** 64:
|
||||
break
|
||||
if (tq * w) % 2 ** 137 > 2 ** 137 - 2 ** 64:
|
||||
print(f"SOLUTION: q={j-342} T[q]={tq} w={w}")
|
||||
found_solution = True
|
||||
if (tq * w) % 2 ** 137 > 2 ** 137 - 2 ** 64:
|
||||
print(f"SOLUTION: q={j-342} T[q]={tq} w={w}")
|
||||
found_solution = True
|
||||
if not found_solution:
|
||||
print("No solutions!")
|
||||
|
||||
@ -95,6 +95,201 @@ int main() {
|
||||
}
|
||||
}
|
||||
|
||||
// char basic test
|
||||
std::vector<char> const char_basic_test_expected{0, 10, 40, 100, 9};
|
||||
std::vector<std::string_view> const char_basic_test{"0", "10 ", "40",
|
||||
"100 with text", "9.999"};
|
||||
|
||||
for (std::size_t i = 0; i < char_basic_test.size(); ++i) {
|
||||
auto const f = char_basic_test[i];
|
||||
char result;
|
||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||
|
||||
if (answer.ec != std::errc()) {
|
||||
std::cerr << "could not convert to char for input: \"" << f
|
||||
<< "\" because of invalid argument" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else if (result != char_basic_test_expected[i]) {
|
||||
std::cerr << "result \"" << f << "\" did not match with expected char: "
|
||||
<< static_cast<int>(char_basic_test_expected[i]) << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// short basic test
|
||||
std::vector<short> const short_basic_test_expected{0, 10, -40, 1001, 9};
|
||||
std::vector<std::string_view> const short_basic_test{
|
||||
"0", "10 ", "-40", "1001 with text", "9.999"};
|
||||
|
||||
for (std::size_t i = 0; i < short_basic_test.size(); ++i) {
|
||||
auto const f = short_basic_test[i];
|
||||
short result;
|
||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||
|
||||
if (answer.ec != std::errc()) {
|
||||
std::cerr << "could not convert to short for input: \"" << f
|
||||
<< "\" because of invalid argument" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else if (result != short_basic_test_expected[i]) {
|
||||
std::cerr << "result \"" << f << "\" did not match with expected short: "
|
||||
<< short_basic_test_expected[i] << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// long basic test
|
||||
std::vector<long> const long_basic_test_expected{0, 10, -40, 1001, 9};
|
||||
std::vector<std::string_view> const long_basic_test{
|
||||
"0", "10 ", "-40", "1001 with text", "9.999"};
|
||||
|
||||
for (std::size_t i = 0; i < long_basic_test.size(); ++i) {
|
||||
auto const f = long_basic_test[i];
|
||||
long result;
|
||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||
|
||||
if (answer.ec != std::errc()) {
|
||||
std::cerr << "could not convert to long for input: \"" << f
|
||||
<< "\" because of invalid argument" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else if (result != long_basic_test_expected[i]) {
|
||||
std::cerr << "result \"" << f << "\" did not match with expected long: "
|
||||
<< long_basic_test_expected[i] << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// long long basic test
|
||||
std::vector<long long> const long_long_basic_test_expected{0, 10, -40, 1001,
|
||||
9};
|
||||
std::vector<std::string_view> const long_long_basic_test{
|
||||
"0", "10 ", "-40", "1001 with text", "9.999"};
|
||||
|
||||
for (std::size_t i = 0; i < long_long_basic_test.size(); ++i) {
|
||||
auto const f = long_long_basic_test[i];
|
||||
long long result;
|
||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||
|
||||
if (answer.ec != std::errc()) {
|
||||
std::cerr << "could not convert to long long for input: \"" << f
|
||||
<< "\" because of invalid argument" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else if (result != long_long_basic_test_expected[i]) {
|
||||
std::cerr << "result \"" << f
|
||||
<< "\" did not match with expected long long: "
|
||||
<< long_long_basic_test_expected[i] << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// unsigned char basic test
|
||||
std::vector<unsigned char> const unsigned_char_basic_test_expected{0, 10, 100,
|
||||
9};
|
||||
std::vector<std::string_view> const unsigned_char_basic_test{
|
||||
"0", "10 ", "100 with text", "9.999"};
|
||||
|
||||
for (std::size_t i = 0; i < unsigned_char_basic_test.size(); ++i) {
|
||||
auto const &f = unsigned_char_basic_test[i];
|
||||
unsigned char result;
|
||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||
if (answer.ec != std::errc()) {
|
||||
std::cerr << "could not convert to unsigned char for input: \"" << f
|
||||
<< "\"" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else if (result != unsigned_char_basic_test_expected[i]) {
|
||||
std::cerr << "result \"" << f
|
||||
<< "\" did not match with expected unsigned char: "
|
||||
<< static_cast<int>(unsigned_char_basic_test_expected[i])
|
||||
<< std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// unsigned short basic test
|
||||
std::vector<unsigned short> const unsigned_short_basic_test_expected{0, 10,
|
||||
1001, 9};
|
||||
std::vector<std::string_view> const unsigned_short_basic_test{
|
||||
"0", "10 ", "1001 with text", "9.999"};
|
||||
|
||||
for (std::size_t i = 0; i < unsigned_short_basic_test.size(); ++i) {
|
||||
auto const &f = unsigned_short_basic_test[i];
|
||||
unsigned short result;
|
||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||
if (answer.ec != std::errc()) {
|
||||
std::cerr << "could not convert to unsigned short for input: \"" << f
|
||||
<< "\"" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else if (result != unsigned_short_basic_test_expected[i]) {
|
||||
std::cerr << "result \"" << f
|
||||
<< "\" did not match with expected unsigned short: "
|
||||
<< unsigned_short_basic_test_expected[i] << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// unsigned long basic test
|
||||
std::vector<unsigned long> const unsigned_long_basic_test_expected{0, 10,
|
||||
1001, 9};
|
||||
std::vector<std::string_view> const unsigned_long_basic_test{
|
||||
"0", "10 ", "1001 with text", "9.999"};
|
||||
|
||||
for (std::size_t i = 0; i < unsigned_long_basic_test.size(); ++i) {
|
||||
auto const &f = unsigned_long_basic_test[i];
|
||||
unsigned long result;
|
||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||
if (answer.ec != std::errc()) {
|
||||
std::cerr << "could not convert to unsigned long for input: \"" << f
|
||||
<< "\"" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else if (result != unsigned_long_basic_test_expected[i]) {
|
||||
std::cerr << "result \"" << f
|
||||
<< "\" did not match with expected unsigned long: "
|
||||
<< unsigned_long_basic_test_expected[i] << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// unsigned long long basic test
|
||||
std::vector<unsigned long long> const unsigned_long_long_basic_test_expected{
|
||||
0, 10, 1001, 9};
|
||||
std::vector<std::string_view> const unsigned_long_long_basic_test{
|
||||
"0", "10 ", "1001 with text", "9.999"};
|
||||
|
||||
for (std::size_t i = 0; i < unsigned_long_long_basic_test.size(); ++i) {
|
||||
auto const &f = unsigned_long_long_basic_test[i];
|
||||
unsigned long long result;
|
||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||
if (answer.ec != std::errc()) {
|
||||
std::cerr << "could not convert to unsigned long long for input: \"" << f
|
||||
<< "\"" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else if (result != unsigned_long_long_basic_test_expected[i]) {
|
||||
std::cerr << "result \"" << f
|
||||
<< "\" did not match with expected unsigned long long: "
|
||||
<< unsigned_long_long_basic_test_expected[i] << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// bool basic test
|
||||
std::vector<bool> const bool_basic_test_expected{false, true};
|
||||
std::vector<std::string_view> const bool_basic_test{"0", "1"};
|
||||
|
||||
for (std::size_t i = 0; i < bool_basic_test.size(); ++i) {
|
||||
auto const &f = bool_basic_test[i];
|
||||
bool result;
|
||||
auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result);
|
||||
if (answer.ec != std::errc()) {
|
||||
std::cerr << "could not convert to bool for input: \"" << f << "\""
|
||||
<< std::endl;
|
||||
return EXIT_FAILURE;
|
||||
} else if (result != bool_basic_test_expected[i]) {
|
||||
std::cerr << "result \"" << f << "\" did not match with expected bool: "
|
||||
<< (bool_basic_test_expected[i] ? "true" : "false")
|
||||
<< std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// int invalid error test
|
||||
std::vector<std::string_view> const int_invalid_argument_test{
|
||||
"text", "text with 1002", "+50", " 50"};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user