Skip to content

Commit b20c420

Browse files
tests: parallelize the exhaustive midpoint sweep across hardware threads
1 parent 7790aa6 commit b20c420

2 files changed

Lines changed: 101 additions & 72 deletions

File tree

tests/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
44

55
include(FetchContent)
66

7+
# Some tests (the exhaustive sweeps) parallelize across std::thread.
8+
set(THREADS_PREFER_PTHREAD_FLAG ON)
9+
find_package(Threads REQUIRED)
10+
711
option(SYSTEM_DOCTEST "Use system copy of doctest" OFF)
812
option(FASTFLOAT_SUPPLEMENTAL_TESTS "Run supplemental tests" ON)
913

@@ -49,6 +53,7 @@ function(fast_float_add_cpp_test TEST_NAME)
4953
target_compile_options(${TEST_NAME} PUBLIC -Wsign-compare -Wshadow -Wwrite-strings -Wpointer-arith -Winit-self -Wconversion -Wsign-conversion)
5054
endif()
5155
target_link_libraries(${TEST_NAME} PUBLIC fast_float supplemental-data)
56+
target_link_libraries(${TEST_NAME} PUBLIC Threads::Threads)
5257
if (NOT SYSTEM_DOCTEST)
5358
target_link_libraries(${TEST_NAME} PUBLIC doctest)
5459
else ()

tests/exhaustive32_midpoint.cpp

Lines changed: 96 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
#include "fast_float/fast_float.h"
22

3+
#include <atomic>
34
#include <cassert>
45
#include <cmath>
56
#include <cstdio>
67
#include <ios>
78
#include <iostream>
89
#include <limits>
910
#include <stdexcept>
11+
#include <thread>
12+
#include <vector>
1013

1114
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__)
1215
// Anything at all that is related to cygwin, msys and so forth will
@@ -74,86 +77,107 @@ void strtof_from_string(char const *st, float &d) {
7477
}
7578
}
7679

77-
bool allvalues() {
80+
// Checks a single 32-bit word (interpreted as a float). Returns true if the
81+
// parser agrees with the reference, false (after logging) on a mismatch.
82+
bool check_word(uint32_t word) {
7883
char buffer[64];
79-
for (uint64_t w = 0; w <= 0xFFFFFFFF; w++) {
80-
float v;
81-
if ((w % 1048576) == 0) {
82-
std::cout << ".";
83-
std::cout.flush();
84-
}
85-
uint32_t word = uint32_t(w);
86-
memcpy(&v, &word, sizeof(v));
87-
if (std::isfinite(v)) {
88-
float nextf = std::nextafterf(v, INFINITY);
89-
if (copysign(1, v) != copysign(1, nextf)) {
90-
continue;
91-
}
92-
if (!std::isfinite(nextf)) {
93-
continue;
94-
}
95-
double v1{v};
96-
assert(float(v1) == v);
97-
double v2{nextf};
98-
assert(float(v2) == nextf);
99-
double midv{v1 + (v2 - v1) / 2};
100-
float expected_midv = float(midv);
84+
float v;
85+
memcpy(&v, &word, sizeof(v));
86+
if (!std::isfinite(v)) {
87+
return true;
88+
}
89+
float nextf = std::nextafterf(v, INFINITY);
90+
if (copysign(1, v) != copysign(1, nextf)) {
91+
return true;
92+
}
93+
if (!std::isfinite(nextf)) {
94+
return true;
95+
}
96+
double v1{v};
97+
assert(float(v1) == v);
98+
double v2{nextf};
99+
assert(float(v2) == nextf);
100+
double midv{v1 + (v2 - v1) / 2};
101+
float expected_midv = float(midv);
101102

102-
char const *string_end = to_string(midv, buffer);
103-
float str_answer;
104-
strtof_from_string(buffer, str_answer);
103+
char const *string_end = to_string(midv, buffer);
104+
float str_answer;
105+
strtof_from_string(buffer, str_answer);
105106

106-
float result_value;
107-
auto result = fast_float::from_chars(buffer, string_end, result_value);
108-
// Starting with version 4.0 for fast_float, we return result_out_of_range
109-
// if the value is either too small (too close to zero) or too large
110-
// (effectively infinity). So std::errc::result_out_of_range is normal for
111-
// well-formed input strings.
112-
if (result.ec != std::errc() &&
113-
result.ec != std::errc::result_out_of_range) {
114-
std::cerr << "parsing error ? " << buffer << std::endl;
115-
return false;
116-
}
117-
if (std::isnan(v)) {
118-
if (!std::isnan(result_value)) {
119-
std::cerr << "not nan" << buffer << std::endl;
120-
std::cerr << "v " << std::hexfloat << v << std::endl;
121-
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
122-
std::cerr << "midv " << std::hexfloat << midv << std::endl;
123-
std::cerr << "expected_midv " << std::hexfloat << expected_midv
124-
<< std::endl;
125-
return false;
126-
}
127-
} else if (copysign(1, result_value) != copysign(1, v)) {
128-
std::cerr << buffer << std::endl;
129-
std::cerr << "v " << std::hexfloat << v << std::endl;
130-
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
131-
std::cerr << "midv " << std::hexfloat << midv << std::endl;
132-
std::cerr << "expected_midv " << std::hexfloat << expected_midv
133-
<< std::endl;
134-
std::cerr << "I got " << std::hexfloat << result_value
135-
<< " but I was expecting " << v << std::endl;
136-
return false;
137-
} else if (result_value != str_answer) {
138-
std::cerr << "no match ? " << buffer << std::endl;
139-
std::cerr << "v " << std::hexfloat << v << std::endl;
140-
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
141-
std::cerr << "midv " << std::hexfloat << midv << std::endl;
142-
std::cerr << "expected_midv " << std::hexfloat << expected_midv
143-
<< std::endl;
144-
std::cout << "started with " << std::hexfloat << midv << std::endl;
145-
std::cout << "round down to " << std::hexfloat << str_answer
146-
<< std::endl;
147-
std::cout << "got back " << std::hexfloat << result_value << std::endl;
148-
std::cout << std::dec;
149-
return false;
150-
}
107+
float result_value;
108+
auto result = fast_float::from_chars(buffer, string_end, result_value);
109+
// Starting with version 4.0 for fast_float, we return result_out_of_range
110+
// if the value is either too small (too close to zero) or too large
111+
// (effectively infinity). So std::errc::result_out_of_range is normal for
112+
// well-formed input strings.
113+
if (result.ec != std::errc() && result.ec != std::errc::result_out_of_range) {
114+
std::cerr << "parsing error ? " << buffer << std::endl;
115+
return false;
116+
}
117+
if (std::isnan(v)) {
118+
if (!std::isnan(result_value)) {
119+
std::cerr << "not nan" << buffer << std::endl;
120+
std::cerr << "v " << std::hexfloat << v << std::endl;
121+
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
122+
std::cerr << "midv " << std::hexfloat << midv << std::endl;
123+
std::cerr << "expected_midv " << std::hexfloat << expected_midv
124+
<< std::endl;
125+
return false;
151126
}
127+
} else if (copysign(1, result_value) != copysign(1, v)) {
128+
std::cerr << buffer << std::endl;
129+
std::cerr << "v " << std::hexfloat << v << std::endl;
130+
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
131+
std::cerr << "midv " << std::hexfloat << midv << std::endl;
132+
std::cerr << "expected_midv " << std::hexfloat << expected_midv
133+
<< std::endl;
134+
std::cerr << "I got " << std::hexfloat << result_value
135+
<< " but I was expecting " << v << std::endl;
136+
return false;
137+
} else if (result_value != str_answer) {
138+
std::cerr << "no match ? " << buffer << std::endl;
139+
std::cerr << "v " << std::hexfloat << v << std::endl;
140+
std::cerr << "v2 " << std::hexfloat << v2 << std::endl;
141+
std::cerr << "midv " << std::hexfloat << midv << std::endl;
142+
std::cerr << "expected_midv " << std::hexfloat << expected_midv
143+
<< std::endl;
144+
std::cout << "started with " << std::hexfloat << midv << std::endl;
145+
std::cout << "round down to " << std::hexfloat << str_answer << std::endl;
146+
std::cout << "got back " << std::hexfloat << result_value << std::endl;
147+
std::cout << std::dec;
148+
return false;
152149
}
153-
std::cout << std::endl;
154150
return true;
155151
}
156152

153+
// Sweeps the whole 2^32 float space, split across hardware threads (the values
154+
// are independent). Returns false as soon as any word mismatches.
155+
bool allvalues() {
156+
unsigned int nthreads = std::thread::hardware_concurrency();
157+
if (nthreads == 0) {
158+
nthreads = 1;
159+
}
160+
std::atomic<bool> ok{true};
161+
std::vector<std::thread> workers;
162+
workers.reserve(nthreads);
163+
for (unsigned int t = 0; t < nthreads; t++) {
164+
workers.emplace_back([t, nthreads, &ok]() {
165+
for (uint64_t w = t;
166+
w <= 0xFFFFFFFF && ok.load(std::memory_order_relaxed);
167+
w += nthreads) {
168+
if (!check_word(uint32_t(w))) {
169+
ok.store(false, std::memory_order_relaxed);
170+
return;
171+
}
172+
}
173+
});
174+
}
175+
for (std::thread &worker : workers) {
176+
worker.join();
177+
}
178+
return ok.load();
179+
}
180+
157181
inline void Assert(bool Assertion) {
158182
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || \
159183
defined(sun) || defined(__sun)

0 commit comments

Comments
 (0)