perf: short-input scalar fallback in convert_utf8_to_utf16{le,be} span overloads#986
Conversation
6952ef7 to
3afc85d
Compare
|
Revised after studying #925 thread, #926, and #957. Previous version of this PR was wrong. I had added the short-input check to Tested it explicitly: an The fix: span overloads in The span overloads are Hard numbers (Apple M-series / haswell emulation, pre-allocated buffers, no heap in loop, 5M reps): Scalar path confirmed being called: Scope: this only helps C++ callers using Happy to run on other platforms if that would help. |
…n overloads The span-based overloads of convert_utf8_to_utf16le/be and their _with_errors variants are simdutf_really_inline (= always_inline) in implementation.h. For short inputs the dispatch path — call to the non-inline convert_utf8_to_utf16le(const char*, size_t, char16_t*), which itself calls get_default_implementation()->... — accounts for the entire measured cost. Since implementation.h already includes the scalar utf8_to_utf16 headers at lines 166–167, the scalar convert<>() template is visible in the inline body and the compiler can inline it too. Add an if (size <= 16) branch to the runtime path of four span overloads: convert_utf8_to_utf16le(span, span) convert_utf8_to_utf16be(span, span) convert_utf8_to_utf16le_with_errors(span, span) convert_utf8_to_utf16be_with_errors(span, span) Benchmarks (upstream shortbench, two new entries that exercise the changed call path directly — run with -DSIMDUTF_CXX_STANDARD=20): ./shortbench --function convert_utf8_to_utf16le (C-style, baseline) ./shortbench --function convert_utf8_to_utf16le_span (span, this PR) Size C-style span ratio 1 9.5 ns 4.2 ns 2.26x FASTER 11 28.9 ns 12.7 ns 2.28x FASTER 21 23.3 ns 24.8 ns ~same 31 39.9 ns 41.4 ns ~same (above threshold, no change) Tests (added to existing tests/span_tests.cpp, 11 new TEST() entries): - ASCII at sizes 0..32 (span matches C-style reference) - 2-byte, 3-byte, 4-byte UTF-8 sequences - Threshold boundary: 14, 15, 16 (scalar branch) and 17, 18 (dispatch) - Exactly 16 bytes of 2-byte sequences — scalar branch - Invalid UTF-8: bad continuation, agree on error code and position - LE vs BE byte order distinction verified All pass on haswell with -DSIMDUTF_CXX_STANDARD=20. The C-style (const char*, size_t) functions remain unchanged. The full- inlining fix for the C-style API requires the ABI-breaking approach described in simdutf#925 (making those functions inline in the header + dispatch:: namespace), which is a separate major-release change. Addresses simdutf#925.
3afc85d to
84fa4c1
Compare
|
Updated What changed in this push:
These call cmake -B build -DSIMDUTF_BENCHMARKS=ON -DSIMDUTF_TESTS=ON -DSIMDUTF_CXX_STANDARD=20
cmake --build build --target shortbench
./build/benchmarks/shortbench --function convert_utf8_to_utf16le # C-style baseline
./build/benchmarks/shortbench --function convert_utf8_to_utf16le_span # span fast pathResults on this machine (Apple / haswell emulation): Above 16 bytes both paths behave identically (expected — the threshold check is false and the span overload falls through to the same C-style function).
cmake -B build -DSIMDUTF_CXX_STANDARD=20
cmake --build build --target span_tests
./build/tests/span_tests
# All 11 new tests: OK
|
|
Running tests. This should be investigated. |
The new assert_equal_utf16_output helper named its parameters lhs/rhs. ASSERT_EQUAL(a, b) declares internal `const auto lhs = (a)` and `const auto rhs = (b)`, so ASSERT_EQUAL(lhs[i], rhs[i]) expanded to `const auto lhs = (lhs[i])`, using lhs in its own initializer. GCC and Clang accepted it but MSVC (VS17, C++20) rejected it with C3536 (used before initialized), C2109 (subscript on non-array), and C2088 (<< on stringstream), failing all four windows-vs17 jobs. Rename the parameters to expected/actual so they no longer collide with the macro's internal names.
What changed
This adds a
<= 16byte scalar fallback to the UTF-8 to UTF-16 span overloads:convert_utf8_to_utf16le(std::span<const char>, std::span<char16_t>)convert_utf8_to_utf16be(std::span<const char>, std::span<char16_t>)convert_utf8_to_utf16le_with_errors(std::span<const char>, std::span<char16_t>)convert_utf8_to_utf16be_with_errors(std::span<const char>, std::span<char16_t>)For those short inputs, the span overload calls the scalar implementation directly instead of going through the selected implementation dispatch path.
Scope
This keeps the optimization on the inline span/ranges path.
It does not change the C-style API, does not add a new public inline API, and does not move the fix into
implementation.cpp. That keeps the patch limited to the non-breaking path discussed in issue#925.Benchmark data
Target reported by
shortbench:haswellThe size
31result is why the cutoff stays conservative. The scalar shortcut helps where dispatch overhead dominates, then the existing selected implementation path becomes competitive again.shortbenchnow has pointer and span entries for the normal and_with_errorsUTF-8 to UTF-16 paths, so each optimized span overload has a direct C-style baseline.Tests
The span tests compare the optimized overloads against the existing pointer API for:
14, 15, 16, 17, 18Local validation: