Hi Paul, On 2026-03-11T14:58:48-0700, Paul Eggert wrote: > On 2026-03-11 05:15, Alejandro Colomar wrote: > > strlen(3) is orders of magnitude faster than what asprintf(3) does. > > I expect it won't be something that can be measured > > On the contrary, if the strings are long enough then on many architectures I > would expect strlen and *sprintf-with-%s to be about the same speed, as the > bottleneck is access to all that slow RAM, not the CPU.
I've ran some tests. They're single-threaded, so RAM contention might
change the results if you have the other cores busy. Feel free to show
such tests.
For the tests, I've used a trivial format string: "%s" (making it
equivalent to strdup(3)). See the source code at the bottom.
For
Here's a table of string sizes, and the relationship of times:
size aprintf+strlen asprintf
1 GB 100% 81%
100 MB 100% 86%
10 MB 100% 70%
1 MB 100% 95%
100 KB 100% 97%
10 KB 100% 77%
1 KB 100% 90%
100 B 100% 86%
10 B 100% 88%
1 B 100% 85%
Here are the raw tests for the results above:
alx@devuan:~/tmp$ time ./a.out 1000 1000000000 0
real 3m13.112s
user 2m7.552s
sys 1m5.537s
alx@devuan:~/tmp$ time ./a.out 1000 1000000000 1
real 2m36.414s
user 1m31.049s
sys 1m5.345s
alx@devuan:~/tmp$ time ./a.out 1000 100000000 0
real 0m23.003s
user 0m12.402s
sys 0m10.599s
alx@devuan:~/tmp$ time ./a.out 1000 100000000 1
real 0m19.830s
user 0m9.200s
sys 0m10.629s
alx@devuan:~/tmp$ time ./a.out 10000 10000000 0
real 0m17.761s
user 0m11.840s
sys 0m5.920s
alx@devuan:~/tmp$ time ./a.out 10000 10000000 1
real 0m12.399s
user 0m7.779s
sys 0m4.619s
alx@devuan:~/tmp$ time ./a.out 100000 1000000 0
real 0m23.294s
user 0m6.428s
sys 0m16.864s
alx@devuan:~/tmp$ time ./a.out 100000 1000000 1
real 0m22.104s
user 0m5.536s
sys 0m16.566s
alx@devuan:~/tmp$ time ./a.out 1000000 100000 0
real 0m24.881s
user 0m6.488s
sys 0m18.390s
alx@devuan:~/tmp$ time ./a.out 1000000 100000 1
real 0m24.192s
user 0m5.456s
sys 0m18.734s
alx@devuan:~/tmp$ time ./a.out 10000000 10000 0
real 0m2.639s
user 0m2.638s
sys 0m0.000s
alx@devuan:~/tmp$ time ./a.out 10000000 10000 1
real 0m2.028s
user 0m2.027s
sys 0m0.001s
alx@devuan:~/tmp$ time ./a.out 100000000 1000 0
real 0m13.656s
user 0m13.655s
sys 0m0.000s
alx@devuan:~/tmp$ time ./a.out 100000000 1000 1
real 0m12.300s
user 0m12.298s
sys 0m0.001s
alx@devuan:~/tmp$ time ./a.out 1000000000 100 0
real 0m22.650s
user 0m22.648s
sys 0m0.000s
alx@devuan:~/tmp$ time ./a.out 1000000000 100 1
real 0m19.591s
user 0m19.589s
sys 0m0.000s
alx@devuan:~/tmp$ time ./a.out 1000000000 10 0
real 0m21.992s
user 0m21.990s
sys 0m0.001s
alx@devuan:~/tmp$ time ./a.out 1000000000 10 1
real 0m19.402s
user 0m19.400s
sys 0m0.001s
alx@devuan:~/tmp$ time ./a.out 1000000000 1 0
real 0m20.057s
user 0m20.050s
sys 0m0.005s
alx@devuan:~/tmp$ time ./a.out 1000000000 1 1
real 0m17.048s
user 0m17.046s
sys 0m0.001s
I think I can explain the fact the the widest gap is around 70% (amost
2/3). asprintf(3) calls twice snprintf(3) --or so I expect; I didn't
check the glibc implementation--. aprintf() requires a third pass over
the string. I expect the limit would indeed be 2/3.
Taking into account these:
- One rarely needs the length of such a string.
- One usually does more operations than just calling a[s]printf().
- One often uses more than just %s.
- To reach the limits, I had to grow the string quite unreasonably.
- The limits aren't terrible; it's not an order of magnitude.
I think asprintf(3) is unnecessarily complex. It might be an
interesting optimized API for a niche use case, but not something the
average programmer should care about, considering the increased
complexity, and the degraded static analysis.
Have a lovely night!
Alex
---
Here's my CPU:
$ lscpu | head
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 46 bits physical, 48 bits
virtual
Byte Order: Little Endian
CPU(s): 24
On-line CPU(s) list: 0-23
Vendor ID: GenuineIntel
Model name: 13th Gen Intel(R) Core(TM)
i9-13900T
CPU family: 6
Model: 183
And below is the test program's source code:
$ cat bench.c
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
[[gnu::noipa]] char *aprintf(const char *restrict fmt, ...);
char *vaprintf(const char *restrict fmt, va_list ap);
int
main(int, char *argv[])
{
char *s;
size_t n, z;
size_t dummy;
n = strtol(argv[1], NULL, 0);
z = strtol(argv[2], NULL, 0);
s = malloc(z);
if (s == NULL)
exit(1);
memset(s, 'x', z);
s[z - 1] = '\0';
dummy = 0;
if (atoi(argv[3]) == 0) {
for (size_t i = 0; i < n; i++) {
char *p;
p = aprintf("%s", s);
if (p == NULL)
exit(1);
dummy += strlen(p);
free(p);
}
} else {
for (size_t i = 0; i < n; i++) {
int l;
char *p;
l = asprintf(&p, "%s", s);
if (l < 0)
exit(1);
dummy += l;
free(p);
}
}
return dummy;
}
char *
aprintf(const char *restrict fmt, ...)
{
char *p;
va_list ap;
va_start(ap, fmt);
p = vaprintf(fmt, ap);
va_end(ap);
return p;
}
char *
vaprintf(const char *restrict fmt, va_list ap)
{
char *p;
if (vasprintf(&p, fmt, ap) == -1)
return NULL;
return p;
}
--
<https://www.alejandro-colomar.es>
signature.asc
Description: PGP signature
