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>

Attachment: signature.asc
Description: PGP signature

Reply via email to