Alejandro Colomar wrote:
> some experiment shows that s+strlen(s) is faster:
> 
>       alx@devuan:~/tmp$ gcc -fno-builtin strnul1.c -o strnul1
>       alx@devuan:~/tmp$ gcc -fno-builtin strnul2.c -o strnul2
>       alx@devuan:~/tmp$ time ./strnul1 1000000000
> 
>       real    0m1.006s
>       user    0m1.002s
>       sys     0m0.004s
>       alx@devuan:~/tmp$ time ./strnul2 1000000000
> 
>       real    0m1.243s
>       user    0m1.239s
>       sys     0m0.004s

I'm adding a benchmark (see attached patch). I get similar results as you do:

With current glibc, on x86_64:

$ gltests/bench-strnul nlc 50 100000000
Test n
real   0.307205
user   0.307
sys    0.000

Test l
real   0.285057
user   0.285
sys    0.000

Test c
real   0.334361
user   0.334
sys    0.000

With current glibc, on i686:

$ gltests/bench-strnul nlc 50 100000000
Test n
real   0.307825
user   0.308
sys    0.000

Test l
real   0.309397
user   0.309
sys    0.000

Test c
real   0.500006
user   0.500
sys    0.000

So, I confirm that strchr is consistently slower, and that the
transformation done by gcc >= 7 and clang >= 4 is actually an optimization.

> > because it says that strchr(s, '\0') is faster than s + strlen(s):
> 
> Huh; I'm curious about why that's said in the glibc manual.

I guess that when Ulrich Drepper wrote this text in 1999, the particular
combination of
  - the i586 processors available at the time,
  - the strlen implementation in glibc at the time,
  - the strchr implementation in glibc at the time
made the strchr expression the fastest one.

Lesson learned: Benchmarks need to be repeated every 5 or 10 years.

Which is why we keep the benchmarks under version control in Gnulib.

Bruno

>From 45bac631c5b12f5bb59353a7806551701a9d3a7d Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sun, 22 Feb 2026 08:58:11 +0100
Subject: [PATCH] strnul-bench-tests: New module.

* tests/bench-strnul.c: New file.
* modules/strnul-bench-tests: New file.
---
 ChangeLog                  |   6 ++
 modules/strnul-bench-tests |  16 +++++
 tests/bench-strnul.c       | 140 +++++++++++++++++++++++++++++++++++++
 3 files changed, 162 insertions(+)
 create mode 100644 modules/strnul-bench-tests
 create mode 100644 tests/bench-strnul.c

diff --git a/ChangeLog b/ChangeLog
index 2c934641da..8eac7b6430 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2026-02-22  Bruno Haible  <[email protected]>
+
+	strnul-bench-tests: New module.
+	* tests/bench-strnul.c: New file.
+	* modules/strnul-bench-tests: New file.
+
 2026-02-21  Paul Eggert  <[email protected]>
 
 	cdefs: omit ungrammatical (and unnecessary) #error
diff --git a/modules/strnul-bench-tests b/modules/strnul-bench-tests
new file mode 100644
index 0000000000..816528d675
--- /dev/null
+++ b/modules/strnul-bench-tests
@@ -0,0 +1,16 @@
+Files:
+tests/bench-strnul.c
+tests/bench-multibyte.h
+tests/bench.h
+
+Depends-on:
+strnul
+striconv
+getrusage
+gettimeofday
+
+configure.ac:
+
+Makefile.am:
+noinst_PROGRAMS += bench-strnul
+bench_strnul_LDADD = $(LDADD) $(LIBICONV)
diff --git a/tests/bench-strnul.c b/tests/bench-strnul.c
new file mode 100644
index 0000000000..539c371682
--- /dev/null
+++ b/tests/bench-strnul.c
@@ -0,0 +1,140 @@
+/* Benchmark for strnul().
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <string.h>
+
+#include "bench.h"
+#include "bench-multibyte.h"
+
+const char * volatile input;
+const char * volatile output;
+
+static _GL_ATTRIBUTE_NOINLINE void
+do_strnul_test (char test, int repeat, const char *text)
+{
+  printf ("Test %c\n", test);
+
+  struct timings_state ts;
+  timing_start (&ts);
+
+  for (int count = 0; count < repeat; count++)
+    {
+      input = text;
+      output = strnul (input);
+    }
+
+  timing_end (&ts);
+  timing_output (&ts);
+  printf ("\n");
+}
+
+static _GL_ATTRIBUTE_NOINLINE void
+do_strlen_test (char test, int repeat, const char *text)
+{
+  printf ("Test %c\n", test);
+
+  size_t (* volatile p_strlen) (const char *) = strlen;
+
+  struct timings_state ts;
+  timing_start (&ts);
+
+  for (int count = 0; count < repeat; count++)
+    {
+      input = text;
+      output = input + p_strlen (input);
+    }
+
+  timing_end (&ts);
+  timing_output (&ts);
+  printf ("\n");
+}
+
+static _GL_ATTRIBUTE_NOINLINE void
+do_strchr_test (char test, int repeat, const char *text)
+{
+  printf ("Test %c\n", test);
+
+  char * (* volatile p_strchr) (const char *, int) = strchr;
+
+  struct timings_state ts;
+  timing_start (&ts);
+
+  for (int count = 0; count < repeat; count++)
+    {
+      input = text;
+      output = p_strchr (input, '\0');
+    }
+
+  timing_end (&ts);
+  timing_output (&ts);
+  printf ("\n");
+}
+
+/* Performs some or all of the following tests:
+     n - strnul
+     l - strlen
+     c - strchr
+   Pass the tests to be performed as first argument.  */
+int
+main (int argc, char *argv[])
+{
+  if (argc != 4)
+    {
+      fprintf (stderr, "Usage: %s TESTS LENGTH REPETITIONS\n", argv[0]);
+      fprintf (stderr, "Example: %s nlc 100 1000000\n", argv[0]);
+      exit (1);
+    }
+
+  const char *tests = argv[1];
+  int length = atoi (argv[2]);
+  int repeat = atoi (argv[3]);
+
+  text_init ();
+  if (length > strlen (text_french_iso8859))
+    {
+      fprintf (stderr, "LENGTH too large, should be <= %zu\n",
+               strlen (text_french_iso8859));
+      exit (1);
+    }
+  const char *text =
+    text_french_iso8859 + strlen (text_french_iso8859) - length;
+
+  /* Execute each test.  */
+  for (size_t i = 0; i < strlen (tests); i++)
+    {
+      char test = tests[i];
+
+      switch (test)
+        {
+        case 'n':
+          do_strnul_test (test, repeat, text);
+          break;
+        case 'l':
+          do_strlen_test (test, repeat, text);
+          break;
+        case 'c':
+          do_strchr_test (test, repeat, text);
+          break;
+        default:
+          /* Ignore.  */
+          ;
+        }
+    }
+
+  return 0;
+}
-- 
2.52.0

Reply via email to