diff --git a/TODO.md b/TODO.md index 7b49f60e4..a6de96598 100644 --- a/TODO.md +++ b/TODO.md @@ -132,6 +132,7 @@ Must resolve before finalising the **1.11.3** release: Misc: * [ ] Bring in **include/stlsoft/string/trim_functions.h** and **test/unit/stlsoft/string/trim_functions** from `mwsis/trim_functions` (on the **STLSoft** clone); * [ ] Bring in the `memchr` / `memcmp` and ASCII case-fold performance work for `strnchr`, `strnicmp`, `strnistrn`, and `strnstrn`, plus **test.performance.stlsoft.string.c_string**, from `mwsis/mem-fns` (on the **STLSoft** clone). The `strnistrn` correctness fix is already in **1.11.2**; +* [ ] Replace the duplicated ASCII case-fold helpers with one shared pair: `stlsoft_C_strnicmp_fold_char_m_()` / `stlsoft_C_strnistrn_fold_char_m_()`, and `stlsoft_C_strnicmp_fold_char_w_()` / `stlsoft_C_strnistrn_fold_char_w_()`; Normalise C API (and related C-facing) identifier prefixes. **House rule** (documented in **c-cpp-standards** → *STLSoft C API identifier prefixes*): diff --git a/include/stlsoft/memory/auto_buffer.hpp b/include/stlsoft/memory/auto_buffer.hpp index 70678cb8b..2418980c9 100644 --- a/include/stlsoft/memory/auto_buffer.hpp +++ b/include/stlsoft/memory/auto_buffer.hpp @@ -4,7 +4,7 @@ * Purpose: Contains the auto_buffer template class. * * Created: 19th January 2002 - * Updated: 23rd October 2026 + * Updated: 25th October 2026 * * Thanks: To Magnificent Imbecil for pointing out error in documentation, * and for suggesting swap() optimisation. To Thorsten Ottosen for @@ -58,7 +58,7 @@ # define STLSOFT_VER_STLSOFT_MEMORY_HPP_AUTO_BUFFER_MAJOR 5 # define STLSOFT_VER_STLSOFT_MEMORY_HPP_AUTO_BUFFER_MINOR 9 # define STLSOFT_VER_STLSOFT_MEMORY_HPP_AUTO_BUFFER_REVISION 1 -# define STLSOFT_VER_STLSOFT_MEMORY_HPP_AUTO_BUFFER_EDIT 235 +# define STLSOFT_VER_STLSOFT_MEMORY_HPP_AUTO_BUFFER_EDIT 236 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ diff --git a/include/stlsoft/string/c_string/strnchr.h b/include/stlsoft/string/c_string/strnchr.h index c479d42e3..1c729b04b 100644 --- a/include/stlsoft/string/c_string/strnchr.h +++ b/include/stlsoft/string/c_string/strnchr.h @@ -4,11 +4,11 @@ * Purpose: Definition of stlsoft_C_strnchr() and stlsoft_C_wcsnchr() * * Created: 1st October 2020 - * Updated: 29th September 2024 + * Updated: 23rd October 2026 * * Home: http://stlsoft.org/ * - * Copyright (c) 2020, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2020-2026, Matthew Wilson and Synesis Information Systems * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,7 +54,7 @@ # define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNCHR_MAJOR 1 # define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNCHR_MINOR 0 # define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNCHR_REVISION 2 -# define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNCHR_EDIT 3 +# define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNCHR_EDIT 5 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -69,6 +69,15 @@ # pragma message(__FILE__) #endif /* STLSOFT_TRACE_INCLUDE */ +#ifndef STLSOFT_INCL_H_STRING +# define STLSOFT_INCL_H_STRING +# include +#endif /* !STLSOFT_INCL_H_STRING */ +#ifndef STLSOFT_INCL_H_WCHAR +# define STLSOFT_INCL_H_WCHAR +# include +#endif /* !STLSOFT_INCL_H_WCHAR */ + /* ///////////////////////////////////////////////////////////////////////// * API functions @@ -82,6 +91,13 @@ stlsoft_C_strnchr( , int ch ) { + /* memchr matches char promotion only when ch is in 0-127. */ + if (0 <= ch && + ch <= 127) + { + return STLSOFT_C_CAST(char const*, STLSOFT_NS_GLOBAL(memchr)(s, ch, n)); + } + for (; 0 != n; --n, ++s) { if (ch == *s) @@ -101,6 +117,17 @@ stlsoft_C_wcsnchr( , int ch ) { + /* wmemchr matches the int comparison only when ch is in 0-127. */ + if (0 <= ch && + ch <= 127) + { + return STLSOFT_NS_GLOBAL(wmemchr)( + s + , STLSOFT_C_CAST(wchar_t, ch) + , n + ); + } + for (; 0 != n; --n, ++s) { if (ch == STLSOFT_C_CAST(int, *s)) diff --git a/include/stlsoft/string/c_string/strnicmp.h b/include/stlsoft/string/c_string/strnicmp.h index 85781e805..021f0d5bd 100644 --- a/include/stlsoft/string/c_string/strnicmp.h +++ b/include/stlsoft/string/c_string/strnicmp.h @@ -4,11 +4,11 @@ * Purpose: Definition of stlsoft_C_strnicmp() and stlsoft_C_wcsnicmp() * * Created: 25th November 2020 - * Updated: 26th October 2026 + * Updated: 27th October 2026 * * Home: http://stlsoft.org/ * - * Copyright (c) 2020-2024, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2020-2026, Matthew Wilson and Synesis Information Systems * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,8 +53,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNICMP_MAJOR 1 # define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNICMP_MINOR 0 -# define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNICMP_REVISION 2 -# define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNICMP_EDIT 4 +# define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNICMP_REVISION 3 +# define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNICMP_EDIT 6 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -69,16 +69,59 @@ # pragma message(__FILE__) #endif /* STLSOFT_TRACE_INCLUDE */ -#ifndef STLSOFT_INCL_H_CTYPE -# define STLSOFT_INCL_H_CTYPE -# include -#endif /* !STLSOFT_INCL_H_CTYPE */ #ifndef STLSOFT_INCL_H_WCTYPE # define STLSOFT_INCL_H_WCTYPE # include #endif /* !STLSOFT_INCL_H_WCTYPE */ +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +#ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION + +/* ASCII letters fold by 32. Every other byte is returned unchanged. */ +/* Duplicate of stlsoft_C_strnistrn_fold_char_m_(). */ +STLSOFT_INLINE +int +stlsoft_C_strnicmp_fold_char_m_( + unsigned char ch +) +{ + if ('a' <= ch && + ch <= 'z') + { + ch = (unsigned char)(ch - ('a' - 'A')); + } + + return (int)ch; +} + +/* ASCII letters fold by 32. Units outside 0-127 use towupper. */ +/* Duplicate of stlsoft_C_strnistrn_fold_char_w_(). */ +STLSOFT_INLINE +int +stlsoft_C_strnicmp_fold_char_w_( + wchar_t ch +) +{ + if (L'a' <= ch && + ch <= L'z') + { + return (int)ch - (L'a' - L'A'); + } + + if ((unsigned long)ch <= 127ul) + { + return (int)ch; + } + + return (int)STLSOFT_NS_GLOBAL(towupper)(ch); +} +#endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ + + /* ///////////////////////////////////////////////////////////////////////// * API functions */ @@ -103,23 +146,22 @@ stlsoft_C_strnicmp( { for (; 0 != n; --n, ++s1, ++s2) { - int d = stlsoft_static_cast(int, *s1) - stlsoft_static_cast(int, *s2); + unsigned char const c1 = (unsigned char)*s1; + unsigned char const c2 = (unsigned char)*s2; - if (0 != d) + if (c1 != c2) { - d = toupper(*s1) - toupper(*s2); - } + int const d = stlsoft_C_strnicmp_fold_char_m_(c1) - + stlsoft_C_strnicmp_fold_char_m_(c2); - if (0 == d) - { - if ('\0' == *s1) + if (0 != d) { - return 0; + return d; } } - else + else if ('\0' == *s1) { - return d; + return 0; } } @@ -146,23 +188,22 @@ stlsoft_C_wcsnicmp( { for (; 0 != n; --n, ++s1, ++s2) { - int d = stlsoft_static_cast(int, *s1) - stlsoft_static_cast(int, *s2); + wchar_t const c1 = *s1; + wchar_t const c2 = *s2; - if (0 != d) + if (c1 != c2) { - d = towupper(*s1) - towupper(*s2); - } + int const d = stlsoft_C_strnicmp_fold_char_w_(c1) - + stlsoft_C_strnicmp_fold_char_w_(c2); - if (0 == d) - { - if ('\0' == *s1) + if (0 != d) { - return 0; + return d; } } - else + else if (L'\0' == c1) { - return d; + return 0; } } diff --git a/include/stlsoft/string/c_string/strnistrn.h b/include/stlsoft/string/c_string/strnistrn.h index 8d4a58414..7a30ff84b 100644 --- a/include/stlsoft/string/c_string/strnistrn.h +++ b/include/stlsoft/string/c_string/strnistrn.h @@ -4,7 +4,7 @@ * Purpose: Definition of stlsoft_C_strnistrn() and stlsoft_C_wcsnistrn() * * Created: 1st October 2020 - * Updated: 23rd October 2026 + * Updated: 27th October 2026 * * Home: http://stlsoft.org/ * @@ -54,7 +54,7 @@ # define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNISTRN_MAJOR 1 # define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNISTRN_MINOR 0 # define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNISTRN_REVISION 4 -# define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNISTRN_EDIT 5 +# define STLSOFT_VER_STLSOFT_STRING_C_STRING_H_STRNISTRN_EDIT 6 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -69,16 +69,59 @@ # pragma message(__FILE__) #endif /* STLSOFT_TRACE_INCLUDE */ -#ifndef STLSOFT_INCL_H_CTYPE -# define STLSOFT_INCL_H_CTYPE -# include -#endif /* !STLSOFT_INCL_H_CTYPE */ #ifndef STLSOFT_INCL_H_WCTYPE # define STLSOFT_INCL_H_WCTYPE # include #endif /* !STLSOFT_INCL_H_WCTYPE */ +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +#ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION + +/* ASCII letters fold by 32. Every other byte is returned unchanged. */ +/* Duplicate of stlsoft_C_strnicmp_fold_char_m_(). */ +STLSOFT_INLINE +int +stlsoft_C_strnistrn_fold_char_m_( + unsigned char ch +) +{ + if ('a' <= ch && + ch <= 'z') + { + ch = (unsigned char)(ch - ('a' - 'A')); + } + + return (int)ch; +} + +/* ASCII letters fold by 32. Units outside 0-127 use towupper. */ +/* Duplicate of stlsoft_C_strnicmp_fold_char_w_(). */ +STLSOFT_INLINE +int +stlsoft_C_strnistrn_fold_char_w_( + wchar_t ch +) +{ + if (L'a' <= ch && + ch <= L'z') + { + return (int)ch - (L'a' - L'A'); + } + + if ((unsigned long)ch <= 127ul) + { + return (int)ch; + } + + return (int)STLSOFT_NS_GLOBAL(towupper)(ch); +} +#endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ + + /* ///////////////////////////////////////////////////////////////////////// * API functions */ @@ -108,16 +151,25 @@ stlsoft_C_strnistrn( } else { + unsigned char const ch0 = (unsigned char)s2[0]; + int const fc0 = stlsoft_C_strnistrn_fold_char_m_(ch0); + for (; n1 >= n2; --n1, ++s1) { - if (*s1 == *s2 || - STLSOFT_NS_GLOBAL(toupper)(*s1) == STLSOFT_NS_GLOBAL(toupper)(*s2)) + unsigned char const h0 = (unsigned char)s1[0]; + + if (h0 == ch0 || + stlsoft_C_strnistrn_fold_char_m_(h0) == fc0) { size_t i; for (i = 1; i != n2; ++i) { - if (STLSOFT_NS_GLOBAL(toupper)(s1[i]) != STLSOFT_NS_GLOBAL(toupper)(s2[i])) + unsigned char const h = (unsigned char)s1[i]; + unsigned char const n = (unsigned char)s2[i]; + + if (stlsoft_C_strnistrn_fold_char_m_(h) != + stlsoft_C_strnistrn_fold_char_m_(n)) { break; } @@ -159,16 +211,22 @@ stlsoft_C_wcsnistrn( } else { + wchar_t const ch0 = s2[0]; + int const fc0 = stlsoft_C_strnistrn_fold_char_w_(ch0); + for (; n1 >= n2; --n1, ++s1) { - if (*s1 == *s2 || - STLSOFT_NS_GLOBAL(towupper)(*s1) == STLSOFT_NS_GLOBAL(towupper)(*s2)) + wchar_t const h0 = s1[0]; + + if (h0 == ch0 || + stlsoft_C_strnistrn_fold_char_w_(h0) == fc0) { size_t i; for (i = 1; i != n2; ++i) { - if (STLSOFT_NS_GLOBAL(towupper)(s1[i]) != STLSOFT_NS_GLOBAL(towupper)(s2[i])) + if (stlsoft_C_strnistrn_fold_char_w_(s1[i]) != + stlsoft_C_strnistrn_fold_char_w_(s2[i])) { break; } diff --git a/include/stlsoft/string/c_string/strnstrn.h b/include/stlsoft/string/c_string/strnstrn.h index 0d4ab4e0c..f717c6a99 100644 --- a/include/stlsoft/string/c_string/strnstrn.h +++ b/include/stlsoft/string/c_string/strnstrn.h @@ -4,7 +4,7 @@ * Purpose: Definition of stlsoft_C_strnstrn() and stlsoft_C_wcsnstrn() * * Created: 1st October 2020 - * Updated: 26th October 2026 + * Updated: 27th October 2026 * * Home: http://stlsoft.org/ * @@ -69,6 +69,15 @@ # pragma message(__FILE__) #endif /* STLSOFT_TRACE_INCLUDE */ +#ifndef STLSOFT_INCL_H_STRING +# define STLSOFT_INCL_H_STRING +# include +#endif /* !STLSOFT_INCL_H_STRING */ +#ifndef STLSOFT_INCL_H_WCHAR +# define STLSOFT_INCL_H_WCHAR +# include +#endif /* !STLSOFT_INCL_H_WCHAR */ + /* ///////////////////////////////////////////////////////////////////////// * API functions @@ -99,24 +108,32 @@ stlsoft_C_strnstrn( } else { - for (; n1 >= n2; --n1, ++s1) + unsigned char const ch0 = (unsigned char)s2[0]; + + /* First byte via memchr. The remaining n2-1 bytes via memcmp. */ + while (n1 >= n2) { - if (*s1 == *s2) + size_t const span = n1 - n2 + 1; + char const* hit; + + hit = STLSOFT_C_CAST(char const*, STLSOFT_NS_GLOBAL(memchr)(s1, ch0, span)); + + if (ss_nullptr_k == hit) { - size_t i; - - for (i = 1; i != n2; ++i) - { - if (s1[i] != s2[i]) - { - break; - } - } - - if (i == n2) - { - return s1; - } + break; + } + + if (1 == n2 || + 0 == STLSOFT_NS_GLOBAL(memcmp)(hit + 1, s2 + 1, n2 - 1)) + { + return hit; + } + + { + size_t const skipped = STLSOFT_C_CAST(size_t, hit - s1) + 1; + + s1 += skipped; + n1 -= skipped; } } @@ -149,24 +166,32 @@ stlsoft_C_wcsnstrn( } else { - for (; n1 >= n2; --n1, ++s1) + wchar_t const ch0 = s2[0]; + + /* First unit via wmemchr. The remaining n2-1 units via wmemcmp. */ + while (n1 >= n2) { - if (*s1 == *s2) + size_t const span = n1 - n2 + 1; + wchar_t const* hit; + + hit = STLSOFT_NS_GLOBAL(wmemchr)(s1, ch0, span); + + if (ss_nullptr_k == hit) + { + break; + } + + if (1 == n2 || + 0 == STLSOFT_NS_GLOBAL(wmemcmp)(hit + 1, s2 + 1, n2 - 1)) { - size_t i; - - for (i = 1; i != n2; ++i) - { - if (s1[i] != s2[i]) - { - break; - } - } - - if (i == n2) - { - return s1; - } + return hit; + } + + { + size_t const skipped = STLSOFT_C_CAST(size_t, hit - s1) + 1; + + s1 += skipped; + n1 -= skipped; } } diff --git a/include/unixstl/filesystem/filesystem_traits.hpp b/include/unixstl/filesystem/filesystem_traits.hpp index b17bedde2..9c1b9fb86 100644 --- a/include/unixstl/filesystem/filesystem_traits.hpp +++ b/include/unixstl/filesystem/filesystem_traits.hpp @@ -61,7 +61,7 @@ # define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_MAJOR 5 # define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_MINOR 0 # define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_REVISION 4 -# define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_EDIT 196 +# define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_EDIT 197 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ diff --git a/strategy/performance/C_STRING.md b/strategy/performance/C_STRING.md new file mode 100644 index 000000000..3924dfd05 --- /dev/null +++ b/strategy/performance/C_STRING.md @@ -0,0 +1,747 @@ +# STLSoft - C-string Performance + +Strategy note for the **STLSoft** C string helpers. Measurements are of **test/performance/stlsoft/string/c_string/main.cpp** (`test.performance.stlsoft.string.c_string`). Library sources are **include/stlsoft/string/c_string/strnicmp.h**, **strnchr.h**, **strnistrn.h**, **strnpbrkn.h**, **strnrchr.h**, and **strnstrn.h**. + +The char forms are timed. The wide forms are not in this program. + + +## Status + +The local mains-power run and the six CI cells of this wave are recorded: `linux-gcc`, `linux-clang`, `macos-gcc`, `macos-clang`, `windows-cl`, and `windows-mingw`. Anchors match within every pair on every run, so each pair did the same work. The local paragraphs describe that machine only. **Summary** is the cross-cell comparison. **Directions so far** is the reading of those tables. Compiler banners are still missing, so that reading is not yet a settled claim. + + +## What was measured + +1,000,000 iterations. Haystack length 96. Case-insensitive compare length 45 (`"The Quick Brown Fox Jumps Over The Lazy Dog!!"`, with the other side case-flipped). The harness times each row twice and prints the second pass. `SIS_PERFTESTS_GROUPGAPS=1` inserts a blank line between groups. + +Printed **nanoseconds** are the total for that pass, not nanoseconds per call. Divide by 1,000,000 for a per-call figure. The **anchor** is the sum of the return values over the pass: comparison sign (`+1`, `0`, `-1`) or byte index, or `-1` for a miss. A matching anchor means both functions did the same work. + +Search positions in the haystack: + +* character near the start: index 3 (`Q`); +* character near the end: index 92 (`Z`); +* needle near the start: index 8 (`brown` / `BROWN`); +* needle near the end: index 84 (`jumps` / `JUMPS`); +* character-set needles are four bytes (`xyzQ`, `xyzZ`, `xyz#`). + +`strpbrk` and `strstr` are NUL-terminated. The fixture places a NUL at index 96, so on this haystack they scan the same 96 bytes as the length-bounded helpers. `strncasecmp` is the non-Windows name of the case-insensitive baseline (`_strnicmp` on Windows). `local_strnrchr` is a reverse scan written in the performance program, the same shape as `stlsoft_C_strnrchr`. `stlsoft_C_strnistrn` has no paired baseline. + + +## Local: Gorbicx-MacBook-Pro, mains power, 27th October 2026 + +Built with `./build_cmake.sh test.performance.stlsoft.string.c_string` and run with `SIS_PERFTESTS_GROUPGAPS=1`. Compiler, optimisation level, and CPU model were not recorded with the log. + + +### `strnicmp` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ------------ | ----------- | ---------- | +| `stlsoft_C_strnicmp` | differ-early | 1,122,791 | 1,000,000 | +| `strncasecmp` | differ-early | 2,506,375 | 1,000,000 | +| `stlsoft_C_strnicmp` | differ-late | 23,609,000 | -1,000,000 | +| `strncasecmp` | differ-late | 29,995,250 | -1,000,000 | +| `stlsoft_C_strnicmp` | equal | 24,446,375 | 0 | +| `strncasecmp` | equal | 27,942,125 | 0 | + +`stlsoft_C_strnicmp` is about 2.2× faster when the strings differ at the first byte, about 1.3× when they differ at the last byte, and about 1.1× when they compare equal. + + +### `strnchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| ------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnchr` | near-start | 1,906,750 | 3,000,000 | +| `memchr` | near-start | 1,905,375 | 3,000,000 | +| `stlsoft_C_strnchr` | near-end | 3,285,250 | 92,000,000 | +| `memchr` | near-end | 3,266,875 | 92,000,000 | +| `stlsoft_C_strnchr` | miss | 3,042,042 | -1,000,000 | +| `memchr` | miss | 3,043,958 | -1,000,000 | + +The three needles are ASCII. `stlsoft_C_strnchr` calls `memchr` for a needle in 0–127, and the totals match `memchr` within about 1%. + + +### `strnistrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnistrn` | near-start | 4,170,750 | 8,000,000 | +| `stlsoft_C_strnistrn` | near-end | 30,920,458 | 84,000,000 | +| `stlsoft_C_strnistrn` | miss | 32,696,042 | -1,000,000 | + +No paired function. A hit at index 8 costs about 4.2 ms for the pass. A hit at index 84 and a full miss both cost about 31–33 ms. + + +### `strnpbrkn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnpbrkn` | near-start | 1,570,750 | 3,000,000 | +| `strpbrk` | near-start | 6,051,041 | 3,000,000 | +| `stlsoft_C_strnpbrkn` | near-end | 28,256,583 | 92,000,000 | +| `strpbrk` | near-end | 111,063,917 | 92,000,000 | +| `stlsoft_C_strnpbrkn` | miss | 22,008,584 | -1,000,000 | +| `strpbrk` | miss | 114,684,875 | -1,000,000 | + +On this four-byte set, `stlsoft_C_strnpbrkn` is about 3.9× faster for both hits and about 5.2× faster on a miss. + + +### `strnrchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnrchr` | near-start | 22,345,375 | 3,000,000 | +| `local_strnrchr` | near-start | 22,353,958 | 3,000,000 | +| `stlsoft_C_strnrchr` | near-end | 1,570,917 | 92,000,000 | +| `local_strnrchr` | near-end | 1,572,625 | 92,000,000 | +| `stlsoft_C_strnrchr` | miss | 23,019,708 | -1,000,000 | +| `local_strnrchr` | miss | 23,153,875 | -1,000,000 | + +The two reverse scans match within about 1%. A hit at index 92 is the short walk. A hit at index 3 and a miss both walk almost the whole haystack. + + +### `strnstrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnstrn` | near-start | 2,381,125 | 8,000,000 | +| `strstr` | near-start | 5,680,209 | 8,000,000 | +| `stlsoft_C_strnstrn` | near-end | 3,370,500 | 84,000,000 | +| `strstr` | near-end | 24,235,375 | 84,000,000 | +| `stlsoft_C_strnstrn` | miss | 3,357,291 | -1,000,000 | +| `strstr` | miss | 25,758,125 | -1,000,000 | + +`stlsoft_C_strnstrn` searches with `memchr` and `memcmp`. It is about 2.4× faster than `strstr` for the early needle, and about 7× faster for the late needle and the miss. + + +## CI: `linux-gcc`, 27th October 2026 + +GitHub-hosted Linux runner. Log path `/home/runner/work/STLSoft/STLSoft/build/test/performance/stlsoft/string/c_string/test.performance.stlsoft.string.c_string`. Compiler version was not in the log. Baseline name `strncasecmp`. + + +### `strnicmp` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ------------ | ----------- | ---------- | +| `stlsoft_C_strnicmp` | differ-early | 1,306,644 | 1,000,000 | +| `strncasecmp` | differ-early | 2,431,603 | 1,000,000 | +| `stlsoft_C_strnicmp` | differ-late | 33,280,865 | -1,000,000 | +| `strncasecmp` | differ-late | 3,354,707 | -1,000,000 | +| `stlsoft_C_strnicmp` | equal | 32,641,089 | 0 | +| `strncasecmp` | equal | 2,959,610 | 0 | + +Early difference: `stlsoft_C_strnicmp` is about 1.9× faster. A compare that walks all 45 bytes goes the other way: `strncasecmp` is about 10× faster on `differ-late` and about 11× faster on `equal`. + + +### `strnchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| ------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnchr` | near-start | 2,408,749 | 3,000,000 | +| `memchr` | near-start | 2,147,924 | 3,000,000 | +| `stlsoft_C_strnchr` | near-end | 2,361,727 | 92,000,000 | +| `memchr` | near-end | 2,127,463 | 92,000,000 | +| `stlsoft_C_strnchr` | miss | 2,594,659 | -1,000,000 | +| `memchr` | miss | 2,613,036 | -1,000,000 | + +Hits are about 11–12% slower than `memchr`. The miss matches. + + +### `strnistrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnistrn` | near-start | 3,513,096 | 8,000,000 | +| `stlsoft_C_strnistrn` | near-end | 21,807,977 | 84,000,000 | +| `stlsoft_C_strnistrn` | miss | 23,401,324 | -1,000,000 | + +No paired function. Early hit about 3.5 ms for the pass. Late hit and miss about 22–23 ms. + + +### `strnpbrkn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnpbrkn` | near-start | 2,578,625 | 3,000,000 | +| `strpbrk` | near-start | 3,125,110 | 3,000,000 | +| `stlsoft_C_strnpbrkn` | near-end | 12,024,270 | 92,000,000 | +| `strpbrk` | near-end | 5,966,191 | 92,000,000 | +| `stlsoft_C_strnpbrkn` | miss | 1,947,021 | -1,000,000 | +| `strpbrk` | miss | 7,771,849 | -1,000,000 | + +Early hit: `stlsoft_C_strnpbrkn` is about 1.2× faster. Late hit: `strpbrk` is about 2× faster. Miss: `stlsoft_C_strnpbrkn` is about 4× faster. + + +### `strnrchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnrchr` | near-start | 8,243,921 | 3,000,000 | +| `local_strnrchr` | near-start | 8,170,841 | 3,000,000 | +| `stlsoft_C_strnrchr` | near-end | 2,076,877 | 92,000,000 | +| `local_strnrchr` | near-end | 2,142,867 | 92,000,000 | +| `stlsoft_C_strnrchr` | miss | 1,667,779 | -1,000,000 | +| `local_strnrchr` | miss | 1,667,698 | -1,000,000 | + +The two reverse scans match. + + +### `strnstrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnstrn` | near-start | 2,137,469 | 8,000,000 | +| `strstr` | near-start | 7,696,324 | 8,000,000 | +| `stlsoft_C_strnstrn` | near-end | 2,403,370 | 84,000,000 | +| `strstr` | near-end | 7,782,854 | 84,000,000 | +| `stlsoft_C_strnstrn` | miss | 2,362,438 | -1,000,000 | +| `strstr` | miss | 4,263,720 | -1,000,000 | + +`stlsoft_C_strnstrn` is about 3.6× faster on the early needle, about 3.2× on the late needle, and about 1.8× on the miss. + + +## CI: `linux-clang`, 27th October 2026 + +GitHub-hosted Linux runner. Same log path shape as `linux-gcc`. Compiler version was not in the log. Baseline name `strncasecmp`. + + +### `strnicmp` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ------------ | ----------- | ---------- | +| `stlsoft_C_strnicmp` | differ-early | 2,352,087 | 1,000,000 | +| `strncasecmp` | differ-early | 5,621,520 | 1,000,000 | +| `stlsoft_C_strnicmp` | differ-late | 54,867,515 | -1,000,000 | +| `strncasecmp` | differ-late | 5,678,476 | -1,000,000 | +| `stlsoft_C_strnicmp` | equal | 58,239,579 | 0 | +| `strncasecmp` | equal | 6,235,392 | 0 | + +Early difference: `stlsoft_C_strnicmp` is about 2.4× faster. Full compares: `strncasecmp` is about 9.7× faster on `differ-late` and about 9.3× faster on `equal`. + + +### `strnchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| ------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnchr` | near-start | 4,683,385 | 3,000,000 | +| `memchr` | near-start | 4,659,881 | 3,000,000 | +| `stlsoft_C_strnchr` | near-end | 5,914,095 | 92,000,000 | +| `memchr` | near-end | 5,915,517 | 92,000,000 | +| `stlsoft_C_strnchr` | miss | 5,921,138 | -1,000,000 | +| `memchr` | miss | 5,914,916 | -1,000,000 | + +The totals match `memchr`. + + +### `strnistrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnistrn` | near-start | 10,443,673 | 8,000,000 | +| `stlsoft_C_strnistrn` | near-end | 70,170,801 | 84,000,000 | +| `stlsoft_C_strnistrn` | miss | 88,993,444 | -1,000,000 | + +No paired function. Early hit about 10 ms for the pass. Late hit about 70 ms. Miss about 89 ms. + + +### `strnpbrkn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnpbrkn` | near-start | 3,416,647 | 3,000,000 | +| `strpbrk` | near-start | 5,287,069 | 3,000,000 | +| `stlsoft_C_strnpbrkn` | near-end | 40,470,816 | 92,000,000 | +| `strpbrk` | near-end | 9,745,945 | 92,000,000 | +| `stlsoft_C_strnpbrkn` | miss | 38,400,505 | -1,000,000 | +| `strpbrk` | miss | 10,286,771 | -1,000,000 | + +Early hit: `stlsoft_C_strnpbrkn` is about 1.5× faster. Late hit: `strpbrk` is about 4.2× faster. Miss: `strpbrk` is about 3.7× faster. + + +### `strnrchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnrchr` | near-start | 21,246,034 | 3,000,000 | +| `local_strnrchr` | near-start | 22,013,291 | 3,000,000 | +| `stlsoft_C_strnrchr` | near-end | 2,548,813 | 92,000,000 | +| `local_strnrchr` | near-end | 3,678,845 | 92,000,000 | +| `stlsoft_C_strnrchr` | miss | 22,545,882 | -1,000,000 | +| `local_strnrchr` | miss | 23,680,623 | -1,000,000 | + +Near-start and miss match within about 5%. On the short walk, `local_strnrchr` is about 1.4× slower. + + +### `strnstrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnstrn` | near-start | 4,671,913 | 8,000,000 | +| `strstr` | near-start | 13,699,301 | 8,000,000 | +| `stlsoft_C_strnstrn` | near-end | 6,226,656 | 84,000,000 | +| `strstr` | near-end | 15,013,356 | 84,000,000 | +| `stlsoft_C_strnstrn` | miss | 7,156,116 | -1,000,000 | +| `strstr` | miss | 9,722,912 | -1,000,000 | + +`stlsoft_C_strnstrn` is about 2.9× faster on the early needle, about 2.4× on the late needle, and about 1.4× on the miss. + + +## CI: `windows-cl`, 27th October 2026 + +GitHub-hosted Windows runner. The log path includes `Release`: `D:\a\STLSoft\STLSoft\build\test\performance\stlsoft\string\c_string\Release\test.performance.stlsoft.string.c_string.exe`. Compiler version was not in the log. Baseline name `_strnicmp`. + + +### `strnicmp` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ------------ | ----------- | ---------- | +| `stlsoft_C_strnicmp` | differ-early | 2,115,700 | 1,000,000 | +| `_strnicmp` | differ-early | 6,005,500 | 1,000,000 | +| `stlsoft_C_strnicmp` | differ-late | 67,217,000 | -1,000,000 | +| `_strnicmp` | differ-late | 58,964,300 | -1,000,000 | +| `stlsoft_C_strnicmp` | equal | 67,159,500 | 0 | +| `_strnicmp` | equal | 57,670,400 | 0 | + +Early difference: `stlsoft_C_strnicmp` is about 2.8× faster. Full compares: `_strnicmp` is about 1.1× faster on `differ-late` and about 1.2× faster on `equal`. + + +### `strnchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| ------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnchr` | near-start | 4,227,900 | 3,000,000 | +| `memchr` | near-start | 4,227,100 | 3,000,000 | +| `stlsoft_C_strnchr` | near-end | 7,072,900 | 92,000,000 | +| `memchr` | near-end | 7,068,500 | 92,000,000 | +| `stlsoft_C_strnchr` | miss | 7,901,100 | -1,000,000 | +| `memchr` | miss | 5,649,600 | -1,000,000 | + +Both hits match `memchr`. The miss is about 1.4× slower than `memchr`. + + +### `strnistrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnistrn` | near-start | 12,717,900 | 8,000,000 | +| `stlsoft_C_strnistrn` | near-end | 125,310,200 | 84,000,000 | +| `stlsoft_C_strnistrn` | miss | 83,978,900 | -1,000,000 | + +No paired function. Early hit about 13 ms for the pass. Late hit about 125 ms. Miss about 84 ms. + + +### `strnpbrkn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnpbrkn` | near-start | 9,904,300 | 3,000,000 | +| `strpbrk` | near-start | 7,802,700 | 3,000,000 | +| `stlsoft_C_strnpbrkn` | near-end | 173,617,700 | 92,000,000 | +| `strpbrk` | near-end | 75,973,400 | 92,000,000 | +| `stlsoft_C_strnpbrkn` | miss | 170,429,600 | -1,000,000 | +| `strpbrk` | miss | 78,826,700 | -1,000,000 | + +`strpbrk` is faster on all three scenarios: about 1.3× on the early hit, about 2.3× on the late hit, and about 2.2× on the miss. + + +### `strnrchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnrchr` | near-start | 40,073,000 | 3,000,000 | +| `local_strnrchr` | near-start | 42,470,500 | 3,000,000 | +| `stlsoft_C_strnrchr` | near-end | 4,252,000 | 92,000,000 | +| `local_strnrchr` | near-end | 4,224,000 | 92,000,000 | +| `stlsoft_C_strnrchr` | miss | 36,445,600 | -1,000,000 | +| `local_strnrchr` | miss | 36,491,200 | -1,000,000 | + +The two reverse scans match within about 6%. + + +### `strnstrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnstrn` | near-start | 5,296,300 | 8,000,000 | +| `strstr` | near-start | 6,715,400 | 8,000,000 | +| `stlsoft_C_strnstrn` | near-end | 15,942,700 | 84,000,000 | +| `strstr` | near-end | 10,608,000 | 84,000,000 | +| `stlsoft_C_strnstrn` | miss | 15,908,300 | -1,000,000 | +| `strstr` | miss | 10,243,700 | -1,000,000 | + +Early needle: `stlsoft_C_strnstrn` is about 1.3× faster. Late needle and miss: `strstr` is about 1.5× faster. + + +## CI: `windows-mingw`, 27th October 2026 + +GitHub-hosted Windows runner. Log path `D:\a\STLSoft\STLSoft\build\test\performance\stlsoft\string\c_string\test.performance.stlsoft.string.c_string.exe`. Compiler version was not in the log. Baseline name `_strnicmp`. + + +### `strnicmp` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ------------ | ----------- | ---------- | +| `stlsoft_C_strnicmp` | differ-early | 2,824,100 | 1,000,000 | +| `_strnicmp` | differ-early | 4,920,600 | 1,000,000 | +| `stlsoft_C_strnicmp` | differ-late | 49,843,900 | -1,000,000 | +| `_strnicmp` | differ-late | 55,206,000 | -1,000,000 | +| `stlsoft_C_strnicmp` | equal | 48,906,500 | 0 | +| `_strnicmp` | equal | 53,434,000 | 0 | + +`stlsoft_C_strnicmp` is faster on all three scenarios: about 1.7× on an early difference, and about 1.1× when the compare walks all 45 bytes. + + +### `strnchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| ------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnchr` | near-start | 3,775,100 | 3,000,000 | +| `memchr` | near-start | 4,178,200 | 3,000,000 | +| `stlsoft_C_strnchr` | near-end | 8,832,200 | 92,000,000 | +| `memchr` | near-end | 8,823,100 | 92,000,000 | +| `stlsoft_C_strnchr` | miss | 11,899,800 | -1,000,000 | +| `memchr` | miss | 9,913,400 | -1,000,000 | + +The early hit is about 10% faster than `memchr`. The late hit matches. The miss is about 1.2× slower than `memchr`. + + +### `strnistrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnistrn` | near-start | 5,942,800 | 8,000,000 | +| `stlsoft_C_strnistrn` | near-end | 30,763,200 | 84,000,000 | +| `stlsoft_C_strnistrn` | miss | 39,626,000 | -1,000,000 | + +No paired function. Early hit about 5.9 ms for the pass. Late hit about 31 ms. Miss about 40 ms. + + +### `strnpbrkn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnpbrkn` | near-start | 3,105,900 | 3,000,000 | +| `strpbrk` | near-start | 9,204,800 | 3,000,000 | +| `stlsoft_C_strnpbrkn` | near-end | 54,639,400 | 92,000,000 | +| `strpbrk` | near-end | 86,318,100 | 92,000,000 | +| `stlsoft_C_strnpbrkn` | miss | 46,111,200 | -1,000,000 | +| `strpbrk` | miss | 75,250,200 | -1,000,000 | + +`stlsoft_C_strnpbrkn` is faster on all three scenarios: about 3.0× on the early hit, and about 1.6× on the late hit and the miss. + + +### `strnrchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnrchr` | near-start | 37,272,200 | 3,000,000 | +| `local_strnrchr` | near-start | 37,243,400 | 3,000,000 | +| `stlsoft_C_strnrchr` | near-end | 3,753,200 | 92,000,000 | +| `local_strnrchr` | near-end | 3,750,600 | 92,000,000 | +| `stlsoft_C_strnrchr` | miss | 33,195,500 | -1,000,000 | +| `local_strnrchr` | miss | 32,295,000 | -1,000,000 | + +The two reverse scans match. On the miss, `local_strnrchr` is about 3% faster. + + +### `strnstrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnstrn` | near-start | 4,077,600 | 8,000,000 | +| `strstr` | near-start | 5,372,700 | 8,000,000 | +| `stlsoft_C_strnstrn` | near-end | 19,505,800 | 84,000,000 | +| `strstr` | near-end | 10,045,600 | 84,000,000 | +| `stlsoft_C_strnstrn` | miss | 26,070,700 | -1,000,000 | +| `strstr` | miss | 10,585,500 | -1,000,000 | + +Early needle: `stlsoft_C_strnstrn` is about 1.3× faster. Late needle: `strstr` is about 1.9× faster. Miss: `strstr` is about 2.5× faster. + + +## CI: `macos-gcc`, 27th October 2026 + +GitHub-hosted macOS runner. Log path `/Users/runner/work/STLSoft/STLSoft/build/test/performance/stlsoft/string/c_string/test.performance.stlsoft.string.c_string`. The job label is `macos-gcc`. The compiler banner was not in the log. Baseline name `strncasecmp`. + + +### `strnicmp` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ------------ | ----------- | ---------- | +| `stlsoft_C_strnicmp` | differ-early | 2,394,458 | 1,000,000 | +| `strncasecmp` | differ-early | 6,480,250 | 1,000,000 | +| `stlsoft_C_strnicmp` | differ-late | 51,592,250 | -1,000,000 | +| `strncasecmp` | differ-late | 62,526,166 | -1,000,000 | +| `stlsoft_C_strnicmp` | equal | 50,033,334 | 0 | +| `strncasecmp` | equal | 70,283,917 | 0 | + +`stlsoft_C_strnicmp` is faster on all three scenarios: about 2.7× on an early difference, about 1.2× on `differ-late`, and about 1.4× on `equal`. + + +### `strnchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| ------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnchr` | near-start | 2,898,250 | 3,000,000 | +| `memchr` | near-start | 2,736,916 | 3,000,000 | +| `stlsoft_C_strnchr` | near-end | 5,399,916 | 92,000,000 | +| `memchr` | near-end | 5,618,667 | 92,000,000 | +| `stlsoft_C_strnchr` | miss | 6,546,416 | -1,000,000 | +| `memchr` | miss | 7,220,917 | -1,000,000 | + +The early hit is about 6% slower than `memchr`. The late hit is about 4% faster. The miss is about 10% faster. + + +### `strnistrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnistrn` | near-start | 9,386,750 | 8,000,000 | +| `stlsoft_C_strnistrn` | near-end | 62,101,083 | 84,000,000 | +| `stlsoft_C_strnistrn` | miss | 67,250,583 | -1,000,000 | + +No paired function. Early hit about 9.4 ms for the pass. Late hit about 62 ms. Miss about 67 ms. + + +### `strnpbrkn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnpbrkn` | near-start | 2,814,000 | 3,000,000 | +| `strpbrk` | near-start | 11,398,458 | 3,000,000 | +| `stlsoft_C_strnpbrkn` | near-end | 53,440,167 | 92,000,000 | +| `strpbrk` | near-end | 233,870,083 | 92,000,000 | +| `stlsoft_C_strnpbrkn` | miss | 49,903,667 | -1,000,000 | +| `strpbrk` | miss | 282,749,459 | -1,000,000 | + +`stlsoft_C_strnpbrkn` is faster on all three scenarios: about 4.1× on the early hit, about 4.4× on the late hit, and about 5.7× on the miss. + + +### `strnrchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnrchr` | near-start | 39,948,000 | 3,000,000 | +| `local_strnrchr` | near-start | 37,833,666 | 3,000,000 | +| `stlsoft_C_strnrchr` | near-end | 3,498,167 | 92,000,000 | +| `local_strnrchr` | near-end | 3,501,333 | 92,000,000 | +| `stlsoft_C_strnrchr` | miss | 40,382,959 | -1,000,000 | +| `local_strnrchr` | miss | 36,178,583 | -1,000,000 | + +The short walk matches. On the long walks `local_strnrchr` is about 6% faster for the early hit and about 12% faster on the miss. + + +### `strnstrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnstrn` | near-start | 3,406,208 | 8,000,000 | +| `strstr` | near-start | 10,469,375 | 8,000,000 | +| `stlsoft_C_strnstrn` | near-end | 5,458,958 | 84,000,000 | +| `strstr` | near-end | 37,513,500 | 84,000,000 | +| `stlsoft_C_strnstrn` | miss | 11,252,917 | -1,000,000 | +| `strstr` | miss | 37,514,000 | -1,000,000 | + +`stlsoft_C_strnstrn` is about 3.1× faster on the early needle, about 6.9× on the late needle, and about 3.3× on the miss. + + +## CI: `macos-clang`, 27th October 2026 + +GitHub-hosted macOS runner. Same log path shape as `macos-gcc`. Compiler version was not in the log. Baseline name `strncasecmp`. + + +### `strnicmp` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ------------ | ----------- | ---------- | +| `stlsoft_C_strnicmp` | differ-early | 1,881,375 | 1,000,000 | +| `strncasecmp` | differ-early | 4,066,208 | 1,000,000 | +| `stlsoft_C_strnicmp` | differ-late | 42,403,042 | -1,000,000 | +| `strncasecmp` | differ-late | 53,013,167 | -1,000,000 | +| `stlsoft_C_strnicmp` | equal | 44,810,917 | 0 | +| `strncasecmp` | equal | 55,570,375 | 0 | + +`stlsoft_C_strnicmp` is faster on all three scenarios: about 2.2× on an early difference, about 1.3× on `differ-late`, and about 1.2× on `equal`. + + +### `strnchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| ------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnchr` | near-start | 2,498,042 | 3,000,000 | +| `memchr` | near-start | 2,513,750 | 3,000,000 | +| `stlsoft_C_strnchr` | near-end | 5,011,708 | 92,000,000 | +| `memchr` | near-end | 5,605,417 | 92,000,000 | +| `stlsoft_C_strnchr` | miss | 6,333,500 | -1,000,000 | +| `memchr` | miss | 6,872,458 | -1,000,000 | + +The early hit matches `memchr`. The late hit is about 12% faster. The miss is about 9% faster. + + +### `strnistrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnistrn` | near-start | 9,103,375 | 8,000,000 | +| `stlsoft_C_strnistrn` | near-end | 61,779,041 | 84,000,000 | +| `stlsoft_C_strnistrn` | miss | 65,838,750 | -1,000,000 | + +No paired function. Early hit about 9.1 ms for the pass. Late hit about 62 ms. Miss about 66 ms. + + +### `strnpbrkn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| --------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnpbrkn` | near-start | 2,856,500 | 3,000,000 | +| `strpbrk` | near-start | 11,621,750 | 3,000,000 | +| `stlsoft_C_strnpbrkn` | near-end | 50,464,459 | 92,000,000 | +| `strpbrk` | near-end | 234,734,667 | 92,000,000 | +| `stlsoft_C_strnpbrkn` | miss | 47,424,500 | -1,000,000 | +| `strpbrk` | miss | 237,860,709 | -1,000,000 | + +`stlsoft_C_strnpbrkn` is faster on all three scenarios: about 4.1× on the early hit, about 4.7× on the late hit, and about 5.0× on the miss. + + +### `strnrchr` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnrchr` | near-start | 34,054,083 | 3,000,000 | +| `local_strnrchr` | near-start | 36,654,167 | 3,000,000 | +| `stlsoft_C_strnrchr` | near-end | 3,205,334 | 92,000,000 | +| `local_strnrchr` | near-end | 3,179,125 | 92,000,000 | +| `stlsoft_C_strnrchr` | miss | 36,397,666 | -1,000,000 | +| `local_strnrchr` | miss | 34,966,208 | -1,000,000 | + +The short walk matches. On the long walk to index 3, `local_strnrchr` is about 8% slower. On the miss it is about 4% faster. + + +### `strnstrn` (total ns) + +| Function | Scenario | Nanoseconds | Anchor | +| -------------------- | ---------- | ----------- | ---------- | +| `stlsoft_C_strnstrn` | near-start | 3,394,084 | 8,000,000 | +| `strstr` | near-start | 10,459,542 | 8,000,000 | +| `stlsoft_C_strnstrn` | near-end | 5,457,875 | 84,000,000 | +| `strstr` | near-end | 36,866,708 | 84,000,000 | +| `stlsoft_C_strnstrn` | miss | 10,954,917 | -1,000,000 | +| `strstr` | miss | 37,858,500 | -1,000,000 | + +`stlsoft_C_strnstrn` is about 3.1× faster on the early needle, about 6.8× on the late needle, and about 3.5× on the miss. + + +## Summary + +One row per platform. For every paired helper the cell is **baseline ÷ STLSoft**, from the total nanoseconds above, to two decimal places. Above 1, the STLSoft function was faster. `stlsoft_C_strnistrn` has no baseline, so that table is the total nanoseconds for the pass. + + +### `strnicmp` + +Baseline is `strncasecmp`, and `_strnicmp` on both Windows cells. + +| Platform | differ-early | differ-late | equal | +| ------------- | ------------ | ----------- | ----- | +| local Mac | 2.23× | 1.27× | 1.14× | +| linux-clang | 2.39× | 0.10× | 0.11× | +| linux-gcc | 1.86× | 0.10× | 0.09× | +| macos-clang | 2.16× | 1.25× | 1.24× | +| macos-gcc | 2.71× | 1.21× | 1.40× | +| windows-cl | 2.84× | 0.88× | 0.86× | +| windows-mingw | 1.74× | 1.11× | 1.09× | + + +### `strnchr` + +Baseline is `memchr`. + +| Platform | near-start | near-end | miss | +| ------------- | ---------- | -------- | ----- | +| local Mac | 1.00× | 0.99× | 1.00× | +| linux-clang | 0.99× | 1.00× | 1.00× | +| linux-gcc | 0.89× | 0.90× | 1.01× | +| macos-clang | 1.01× | 1.12× | 1.09× | +| macos-gcc | 0.94× | 1.04× | 1.10× | +| windows-cl | 1.00× | 1.00× | 0.72× | +| windows-mingw | 1.11× | 1.00× | 0.83× | + + +### `strnistrn` + +Total nanoseconds. No paired baseline. + +| Platform | near-start | near-end | miss | +| ------------- | ---------- | ----------- | ---------- | +| local Mac | 4,170,750 | 30,920,458 | 32,696,042 | +| linux-clang | 10,443,673 | 70,170,801 | 88,993,444 | +| linux-gcc | 3,513,096 | 21,807,977 | 23,401,324 | +| macos-clang | 9,103,375 | 61,779,041 | 65,838,750 | +| macos-gcc | 9,386,750 | 62,101,083 | 67,250,583 | +| windows-cl | 12,717,900 | 125,310,200 | 83,978,900 | +| windows-mingw | 5,942,800 | 30,763,200 | 39,626,000 | + + +### `strnpbrkn` + +Baseline is `strpbrk`. + +| Platform | near-start | near-end | miss | +| ------------- | ---------- | -------- | ----- | +| local Mac | 3.85× | 3.93× | 5.21× | +| linux-clang | 1.55× | 0.24× | 0.27× | +| linux-gcc | 1.21× | 0.50× | 3.99× | +| macos-clang | 4.07× | 4.65× | 5.02× | +| macos-gcc | 4.05× | 4.38× | 5.67× | +| windows-cl | 0.79× | 0.44× | 0.46× | +| windows-mingw | 2.96× | 1.58× | 1.63× | + + +### `strnrchr` + +Baseline is `local_strnrchr`. + +| Platform | near-start | near-end | miss | +| ------------- | ---------- | -------- | ----- | +| local Mac | 1.00× | 1.00× | 1.01× | +| linux-clang | 1.04× | 1.44× | 1.05× | +| linux-gcc | 0.99× | 1.03× | 1.00× | +| macos-clang | 1.08× | 0.99× | 0.96× | +| macos-gcc | 0.95× | 1.00× | 0.90× | +| windows-cl | 1.06× | 0.99× | 1.00× | +| windows-mingw | 1.00× | 1.00× | 0.97× | + + +### `strnstrn` + +Baseline is `strstr`. + +| Platform | near-start | near-end | miss | +| ------------- | ---------- | -------- | ----- | +| local Mac | 2.39× | 7.19× | 7.67× | +| linux-clang | 2.93× | 2.41× | 1.36× | +| linux-gcc | 3.60× | 3.24× | 1.80× | +| macos-clang | 3.08× | 6.75× | 3.46× | +| macos-gcc | 3.07× | 6.87× | 3.33× | +| windows-cl | 1.27× | 0.67× | 0.64× | +| windows-mingw | 1.32× | 0.52× | 0.41× | + + +## Directions so far + +Local Mac plus `linux-gcc`, `linux-clang`, `macos-gcc`, `macos-clang`, `windows-cl`, and `windows-mingw`. + +* **Anchors match** on every row of every run. +* **Early `strnicmp` favours `stlsoft_C_strnicmp`** on all seven runs, about 1.7–2.8×. +* **A 45-byte compare splits by C library.** Linux `strncasecmp` is about 9–11× faster. `windows-cl` `_strnicmp` is about 1.1–1.2× faster. The local Mac, both macOS cells, and `windows-mingw` favour `stlsoft_C_strnicmp` by about 1.1–1.4×. +* **`stlsoft_C_strnchr` stays close to `memchr`.** Most rows are within about 12%. The larger gaps are the misses on `windows-cl` (about 1.4× slower) and `windows-mingw` (about 1.2× slower). +* **`stlsoft_C_strnrchr` tracks `local_strnrchr`.** Linux Clang's short walk is the outlier: the local scan is about 1.4× slower there. The macOS long walks differ by up to about 12%. +* **`strnpbrkn` against `strpbrk` depends on the host.** The local Mac, both macOS cells, and `windows-mingw` favour `stlsoft_C_strnpbrkn` on every scenario. On the Apple cells the long scans are about 3.9–5.7×. On MinGW they are about 1.6×. Both Linux cells favour `stlsoft_C_strnpbrkn` on the early hit and favour `strpbrk` on the long scan. `windows-cl` favours `strpbrk` on all three. +* **Early `strnstrn` favours `stlsoft_C_strnstrn`** on all seven runs, about 1.3–3.6×. Late needle and miss favour it on the Mac, both macOS cells, and both Linux cells. They favour `strstr` on both Windows cells: about 1.5× on `windows-cl`, and about 1.9–2.5× on `windows-mingw`. + + +## Not yet done + +* Record compiler banners for each cell, and the local `CMAKE_BUILD_TYPE`. The `macos-gcc` log does not say whether that job's `gcc` is GNU or Apple Clang. +* Write the settled performance claim from the seven runs above, once those banners are known. + + + + diff --git a/test/performance/stlsoft/string/CMakeLists.txt b/test/performance/stlsoft/string/CMakeLists.txt index f7818ca72..a453a77c9 100644 --- a/test/performance/stlsoft/string/CMakeLists.txt +++ b/test/performance/stlsoft/string/CMakeLists.txt @@ -1,5 +1,6 @@ if(X_CMAKE_CXX_FULLSTANDARD GREATER_EQUAL 2011) + add_subdirectory(c_string) add_subdirectory(simple_string.compare) add_subdirectory(simple_string.op_eq) endif() diff --git a/test/performance/stlsoft/string/c_string/CMakeLists.txt b/test/performance/stlsoft/string/c_string/CMakeLists.txt new file mode 100644 index 000000000..e569aad7f --- /dev/null +++ b/test/performance/stlsoft/string/c_string/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_example_program(test.performance.stlsoft.string.c_string main.cpp) diff --git a/test/performance/stlsoft/string/c_string/main.cpp b/test/performance/stlsoft/string/c_string/main.cpp new file mode 100644 index 000000000..845e4377b --- /dev/null +++ b/test/performance/stlsoft/string/c_string/main.cpp @@ -0,0 +1,601 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/performance/stlsoft/string/c_string/main.cpp + * + * Purpose: Perf-test for stlsoft/string/c_string functions. + * + * Created: 20th October 2026 + * Updated: 20th October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * compatibility + */ + +#if !defined(__cplusplus) || \ + __cplusplus < 201103L +# error Requires C++11 or later +#endif + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include +#include +#include +#include +#include +#include + +#if __cplusplus >= 201103L +# include +#else +# include +#endif /* C++11+ */ +#include + +#include "perf_group_gaps.hpp" + +#include +#include +#include + +#include +#include +#if !defined(_WIN32) +# include +#endif /* !_WIN32 */ + + +/* ///////////////////////////////////////////////////////////////////////// + * types + */ + +#if __cplusplus >= 201103L + +typedef stlsoft::std_chrono_hrc_stopwatch stopwatch_t; +#else + +typedef platformstl::stopwatch stopwatch_t; +#endif /* C++11+ */ +typedef stopwatch_t::interval_type interval_t; + +using stlsoft::ss_sint64_t; +using stlsoft::ss_size_t; + +typedef ss_sint64_t (*timed_fn_t)(); + + +/* ///////////////////////////////////////////////////////////////////////// + * constants + */ + +namespace { + + ss_size_t const NUM_ITERATIONS = 1000000; + ss_size_t const HAY_N = 96; + int const FN_COL = 22; + int const SC_COL = 12; + int const NS_COL = 16; + int const AN_COL = 16; + + char const PATTERN[] = "The Quick Brown Fox Jumps Over The Lazy Dog!!"; + ss_size_t const CMP_N = sizeof(PATTERN) - 1; + + char const NEEDLE_EARLY[] = "brown"; + char const NEEDLE_LATE[] = "jumps"; + char const NEEDLE_MISS[] = "####"; + char const NEEDLE_EARLY_CI[] = "BROWN"; + char const NEEDLE_LATE_CI[] = "JUMPS"; + + char const SET_EARLY[] = "xyzQ"; + char const SET_LATE[] = "xyzZ"; + char const SET_MISS[] = "xyz#"; + + ss_size_t const IX_EARLY_CH = 3; + ss_size_t const IX_LATE_CH = HAY_N - 4; + ss_size_t const IX_EARLY_ND = 8; + ss_size_t const IX_LATE_ND = HAY_N - 12; + + char haystack[128]; + char cmp_lhs[64]; + char cmp_rhs_equal[64]; + char cmp_rhs_early[64]; + char cmp_rhs_late[64]; + +#if defined(_WIN32) + char const BASELINE_STRNICMP_NAME[] = "_strnicmp"; +#else + char const BASELINE_STRNICMP_NAME[] = "strncasecmp"; +#endif +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * functions + */ + +/* Times the char forms of the c_string helpers on one fixed haystack. Where + * a C-library function does the same work it is timed beside the helper: + * memchr, strpbrk, strstr, and strncasecmp or _strnicmp. strnrchr is timed + * beside a local reverse scan. strnistrn is timed on its own. + */ + +namespace { + + STLSOFT_STATIC_ASSERT(CMP_N < sizeof(cmp_lhs)); + STLSOFT_STATIC_ASSERT(HAY_N + 1 <= sizeof(haystack)); + STLSOFT_STATIC_ASSERT(IX_EARLY_ND + 5 <= IX_LATE_ND); + STLSOFT_STATIC_ASSERT(IX_LATE_ND + 5 <= IX_LATE_CH); + + + template + std::string + thousands( + T_integer const& v + ) + { + char dest[41]; + size_t n = stlsoft::format_thousands(dest, STLSOFT_NUM_ELEMENTS(dest), "3;0", v); + + /* format_thousands() counts the terminating NUL. */ + if (0 != n) + { + --n; + } + + return std::string(dest, n); + } + + char + flip_alpha_case( + char c + ) + { + if ('a' <= c && c <= 'z') + { + return static_cast(c - 'a' + 'A'); + } + + if ('A' <= c && c <= 'Z') + { + return static_cast(c - 'A' + 'a'); + } + + return c; + } + + ss_sint64_t + ptr_anchor( + char const* base + , void const* p + ) + { + if (NULL == p) + { + return -1; + } + + return static_cast(static_cast(p) - base); + } + + ss_sint64_t + sign_of( + int r + ) + { + if (0 < r) + { + return +1; + } + + if (r < 0) + { + return -1; + } + + return 0; + } + + char const* + local_strnrchr( + char const* s + , size_t n + , int ch + ) + { + if (0 != n) + { + size_t i; + char const* p; + + for (i = 0, p = s + (n - 1); i != n; ++i, --p) + { + if (ch == *p) + { + return p; + } + } + } + + return NULL; + } + + int + baseline_strnicmp( + char const* s1 + , char const* s2 + , size_t n + ) + { +#if defined(_WIN32) + + return ::_strnicmp(s1, s2, n); +#else + + return ::strncasecmp(s1, s2, n); +#endif + } + + void + prepare_inputs() + { + ::memset(haystack, 'a', sizeof(haystack)); + + haystack[HAY_N] = '\0'; + haystack[IX_EARLY_CH] = 'Q'; + haystack[IX_LATE_CH] = 'Z'; + ::memcpy(haystack + IX_EARLY_ND, NEEDLE_EARLY, sizeof(NEEDLE_EARLY) - 1); + ::memcpy(haystack + IX_LATE_ND, NEEDLE_LATE, sizeof(NEEDLE_LATE) - 1); + + ::memcpy(cmp_lhs, PATTERN, CMP_N); + cmp_lhs[CMP_N] = '\0'; + + for (ss_size_t i = 0; CMP_N != i; ++i) + { + cmp_rhs_equal[i] = flip_alpha_case(cmp_lhs[i]); + } + + cmp_rhs_equal[CMP_N] = '\0'; + + ::memcpy(cmp_rhs_early, cmp_rhs_equal, CMP_N + 1); + ::memcpy(cmp_rhs_late, cmp_rhs_equal, CMP_N + 1); + cmp_rhs_early[0] = '#'; + cmp_rhs_late[CMP_N - 1] = '#'; + } + + + ss_sint64_t fn_strnicmp_stl_early() + { + return sign_of(stlsoft_C_strnicmp(cmp_lhs, cmp_rhs_early, CMP_N)); + } + + ss_sint64_t fn_strnicmp_crt_early() + { + return sign_of(baseline_strnicmp(cmp_lhs, cmp_rhs_early, CMP_N)); + } + + ss_sint64_t fn_strnicmp_stl_late() + { + return sign_of(stlsoft_C_strnicmp(cmp_lhs, cmp_rhs_late, CMP_N)); + } + + ss_sint64_t fn_strnicmp_crt_late() + { + return sign_of(baseline_strnicmp(cmp_lhs, cmp_rhs_late, CMP_N)); + } + + ss_sint64_t fn_strnicmp_stl_equal() + { + return sign_of(stlsoft_C_strnicmp(cmp_lhs, cmp_rhs_equal, CMP_N)); + } + + ss_sint64_t fn_strnicmp_crt_equal() + { + return sign_of(baseline_strnicmp(cmp_lhs, cmp_rhs_equal, CMP_N)); + } + + ss_sint64_t fn_strnchr_stl_start() + { + return ptr_anchor(haystack, stlsoft_C_strnchr(haystack, HAY_N, 'Q')); + } + + ss_sint64_t fn_strnchr_crt_start() + { + return ptr_anchor(haystack, ::memchr(haystack, 'Q', HAY_N)); + } + + ss_sint64_t fn_strnchr_stl_end() + { + return ptr_anchor(haystack, stlsoft_C_strnchr(haystack, HAY_N, 'Z')); + } + + ss_sint64_t fn_strnchr_crt_end() + { + return ptr_anchor(haystack, ::memchr(haystack, 'Z', HAY_N)); + } + + ss_sint64_t fn_strnchr_stl_miss() + { + return ptr_anchor(haystack, stlsoft_C_strnchr(haystack, HAY_N, '#')); + } + + ss_sint64_t fn_strnchr_crt_miss() + { + return ptr_anchor(haystack, ::memchr(haystack, '#', HAY_N)); + } + + ss_sint64_t fn_strnistrn_stl_start() + { + return ptr_anchor(haystack, stlsoft_C_strnistrn(haystack, HAY_N, NEEDLE_EARLY_CI, sizeof(NEEDLE_EARLY_CI) - 1)); + } + + ss_sint64_t fn_strnistrn_stl_end() + { + return ptr_anchor(haystack, stlsoft_C_strnistrn(haystack, HAY_N, NEEDLE_LATE_CI, sizeof(NEEDLE_LATE_CI) - 1)); + } + + ss_sint64_t fn_strnistrn_stl_miss() + { + return ptr_anchor(haystack, stlsoft_C_strnistrn(haystack, HAY_N, NEEDLE_MISS, sizeof(NEEDLE_MISS) - 1)); + } + + ss_sint64_t fn_strnpbrkn_stl_start() + { + return ptr_anchor(haystack, stlsoft_C_strnpbrkn(haystack, HAY_N, SET_EARLY, sizeof(SET_EARLY) - 1)); + } + + ss_sint64_t fn_strnpbrkn_crt_start() + { + return ptr_anchor(haystack, ::strpbrk(haystack, SET_EARLY)); + } + + ss_sint64_t fn_strnpbrkn_stl_end() + { + return ptr_anchor(haystack, stlsoft_C_strnpbrkn(haystack, HAY_N, SET_LATE, sizeof(SET_LATE) - 1)); + } + + ss_sint64_t fn_strnpbrkn_crt_end() + { + return ptr_anchor(haystack, ::strpbrk(haystack, SET_LATE)); + } + + ss_sint64_t fn_strnpbrkn_stl_miss() + { + return ptr_anchor(haystack, stlsoft_C_strnpbrkn(haystack, HAY_N, SET_MISS, sizeof(SET_MISS) - 1)); + } + + ss_sint64_t fn_strnpbrkn_crt_miss() + { + return ptr_anchor(haystack, ::strpbrk(haystack, SET_MISS)); + } + + ss_sint64_t fn_strnrchr_stl_start() + { + return ptr_anchor(haystack, stlsoft_C_strnrchr(haystack, HAY_N, 'Q')); + } + + ss_sint64_t fn_strnrchr_loc_start() + { + return ptr_anchor(haystack, local_strnrchr(haystack, HAY_N, 'Q')); + } + + ss_sint64_t fn_strnrchr_stl_end() + { + return ptr_anchor(haystack, stlsoft_C_strnrchr(haystack, HAY_N, 'Z')); + } + + ss_sint64_t fn_strnrchr_loc_end() + { + return ptr_anchor(haystack, local_strnrchr(haystack, HAY_N, 'Z')); + } + + ss_sint64_t fn_strnrchr_stl_miss() + { + return ptr_anchor(haystack, stlsoft_C_strnrchr(haystack, HAY_N, '#')); + } + + ss_sint64_t fn_strnrchr_loc_miss() + { + return ptr_anchor(haystack, local_strnrchr(haystack, HAY_N, '#')); + } + + ss_sint64_t fn_strnstrn_stl_start() + { + return ptr_anchor(haystack, stlsoft_C_strnstrn(haystack, HAY_N, NEEDLE_EARLY, sizeof(NEEDLE_EARLY) - 1)); + } + + ss_sint64_t fn_strnstrn_crt_start() + { + return ptr_anchor(haystack, ::strstr(haystack, NEEDLE_EARLY)); + } + + ss_sint64_t fn_strnstrn_stl_end() + { + return ptr_anchor(haystack, stlsoft_C_strnstrn(haystack, HAY_N, NEEDLE_LATE, sizeof(NEEDLE_LATE) - 1)); + } + + ss_sint64_t fn_strnstrn_crt_end() + { + return ptr_anchor(haystack, ::strstr(haystack, NEEDLE_LATE)); + } + + ss_sint64_t fn_strnstrn_stl_miss() + { + return ptr_anchor(haystack, stlsoft_C_strnstrn(haystack, HAY_N, NEEDLE_MISS, sizeof(NEEDLE_MISS) - 1)); + } + + ss_sint64_t fn_strnstrn_crt_miss() + { + return ptr_anchor(haystack, ::strstr(haystack, NEEDLE_MISS)); + } + + + struct row_spec + { + char const* group; + char const* function_name; + char const* scenario; + timed_fn_t fn; + }; + + row_spec const ROWS[] = + { + { "strnicmp", "stlsoft_C_strnicmp", "differ-early", fn_strnicmp_stl_early, }, + { "strnicmp", BASELINE_STRNICMP_NAME, "differ-early", fn_strnicmp_crt_early, }, + { "strnicmp", "stlsoft_C_strnicmp", "differ-late", fn_strnicmp_stl_late, }, + { "strnicmp", BASELINE_STRNICMP_NAME, "differ-late", fn_strnicmp_crt_late, }, + { "strnicmp", "stlsoft_C_strnicmp", "equal", fn_strnicmp_stl_equal, }, + { "strnicmp", BASELINE_STRNICMP_NAME, "equal", fn_strnicmp_crt_equal, }, + + { "strnchr", "stlsoft_C_strnchr", "near-start", fn_strnchr_stl_start, }, + { "strnchr", "memchr", "near-start", fn_strnchr_crt_start, }, + { "strnchr", "stlsoft_C_strnchr", "near-end", fn_strnchr_stl_end, }, + { "strnchr", "memchr", "near-end", fn_strnchr_crt_end, }, + { "strnchr", "stlsoft_C_strnchr", "miss", fn_strnchr_stl_miss, }, + { "strnchr", "memchr", "miss", fn_strnchr_crt_miss, }, + + { "strnistrn", "stlsoft_C_strnistrn", "near-start", fn_strnistrn_stl_start, }, + { "strnistrn", "stlsoft_C_strnistrn", "near-end", fn_strnistrn_stl_end, }, + { "strnistrn", "stlsoft_C_strnistrn", "miss", fn_strnistrn_stl_miss, }, + + { "strnpbrkn", "stlsoft_C_strnpbrkn", "near-start", fn_strnpbrkn_stl_start, }, + { "strnpbrkn", "strpbrk", "near-start", fn_strnpbrkn_crt_start, }, + { "strnpbrkn", "stlsoft_C_strnpbrkn", "near-end", fn_strnpbrkn_stl_end, }, + { "strnpbrkn", "strpbrk", "near-end", fn_strnpbrkn_crt_end, }, + { "strnpbrkn", "stlsoft_C_strnpbrkn", "miss", fn_strnpbrkn_stl_miss, }, + { "strnpbrkn", "strpbrk", "miss", fn_strnpbrkn_crt_miss, }, + + { "strnrchr", "stlsoft_C_strnrchr", "near-start", fn_strnrchr_stl_start, }, + { "strnrchr", "local_strnrchr", "near-start", fn_strnrchr_loc_start, }, + { "strnrchr", "stlsoft_C_strnrchr", "near-end", fn_strnrchr_stl_end, }, + { "strnrchr", "local_strnrchr", "near-end", fn_strnrchr_loc_end, }, + { "strnrchr", "stlsoft_C_strnrchr", "miss", fn_strnrchr_stl_miss, }, + { "strnrchr", "local_strnrchr", "miss", fn_strnrchr_loc_miss, }, + + { "strnstrn", "stlsoft_C_strnstrn", "near-start", fn_strnstrn_stl_start, }, + { "strnstrn", "strstr", "near-start", fn_strnstrn_crt_start, }, + { "strnstrn", "stlsoft_C_strnstrn", "near-end", fn_strnstrn_stl_end, }, + { "strnstrn", "strstr", "near-end", fn_strnstrn_crt_end, }, + { "strnstrn", "stlsoft_C_strnstrn", "miss", fn_strnstrn_stl_miss, }, + { "strnstrn", "strstr", "miss", fn_strnstrn_crt_miss, }, + }; + + + interval_t + time_calls( + timed_fn_t fn + , ss_sint64_t& anchor + ) + { + /* Volatile reload keeps the call in the loop. The callee is an + * indirect target, so the scan is not hoisted out of the timing. + */ + timed_fn_t volatile vf = fn; + stopwatch_t sw; + ss_sint64_t local = 0; + + sw.start(); + + for (ss_size_t i = 0; NUM_ITERATIONS != i; ++i) + { + timed_fn_t const call = vf; + + local += call(); + } + + sw.stop(); + + anchor = local; + + return sw.get_nanoseconds(); + } + + void + emit_header() + { + std::cout + << '\t' + << std::setw(FN_COL) << std::left << "function" + << '\t' + << std::setw(SC_COL) << std::left << "scenario" + << '\t' + << std::setw(NS_COL) << std::right << "nanoseconds" + << '\t' + << std::setw(AN_COL) << std::right << "anchor" + << std::endl; + } + + void + emit_row( + char const* function_name + , char const* scenario + , interval_t nanoseconds + , ss_sint64_t anchor + ) + { + std::cout + << '\t' + << std::setw(FN_COL) << std::left << function_name + << '\t' + << std::setw(SC_COL) << std::left << scenario + << '\t' + << std::setw(NS_COL) << std::right << thousands(nanoseconds) + << '\t' + << std::setw(AN_COL) << std::right << thousands(anchor) + << std::endl; + } + + void + measure( + row_spec const& row + ) + { + interval_t ns = 0; + ss_sint64_t anchor = 0; + + perf_maybe_emit_group_gap(std::cout, row.group); + + for (int w = 2; 0 != w; --w) + { + ns = time_calls(row.fn, anchor); + } + + emit_row(row.function_name, row.scenario, ns, anchor); + } +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * main() + */ + +int main(int /*argc*/, char* /*argv*/[]) +{ + prepare_inputs(); + + perf_maybe_emit_groupgaps_banner(std::cout); + + std::cout + << "c_string" + << "\titerations=" << thousands(NUM_ITERATIONS) + << "\thaystack=" << HAY_N + << "\tcompare=" << CMP_N + << std::endl; + + emit_header(); + + for (ss_size_t i = 0; i != STLSOFT_NUM_ELEMENTS(ROWS); ++i) + { + measure(ROWS[i]); + } + + return EXIT_SUCCESS; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ +