https://www.etalabs.net/compare_libcs.html
Comparison of C/POSIX standard library implementations for Linux
A project of Eta Labs.
The table below and notes which follow are a comparison of some of
the different standard library implementations available for Linux,
with a particular focus on the balance between feature-richness and
bloat. I have tried to be fair and objective, but as I am the author
of musl, that may have influenced my choice of which aspects to
compare.
Future directions for this comparison include detailed performance
benchmarking and inclusion of additional library implementations,
especially Google's Bionic and other BSD libc ports.
Bloat comparison musl uClibc dietlibc glibc
Complete .a set 426k 500k 120k 2.0M +
Complete .so set 527k 560k 185k 7.9M +
Smallest static C 1.8k 5k 0.2k 662k
program
Static hello (using 13k 70k 6k 662k
printf)
Dynamic overhead 20k 40k 40k 48k
(min. dirty)
Static overhead 8k 12k 8k 28k
(min. dirty)
Static stdio
overhead (min. 8k 24k 16k 36k
dirty)
Configurable no yes minimal minimal
featureset
Behavior on resource musl uClibc dietlibc glibc
exhaustion
Thread-local storage reports aborts n/a aborts
failure
SIGEV_THREAD timers no failure n/a n/a lost overruns
pthread_cancel no failure aborts n/a aborts
regcomp and regexec reports crashes reports crashes
failure failure
fnmatch no failure unknown no failure reports
failure
printf family no failure no failure no failure reports
failure
strtol family no failure no failure no failure no failure
Performance musl uClibc dietlibc glibc
comparison
Tiny allocation & 0.005 0.004 0.013 0.002
free
Big allocation & 0.027 0.018 0.023 0.016
free
Allocation 0.048 0.134 0.393 0.041
contention, local
Allocation 0.050 0.132 0.394 0.062
contention, shared
Zero-fill (memset) 0.023 0.048 0.055 0.012
String length 0.081 0.098 0.161 0.048
(strlen)
Byte search (strchr) 0.142 0.243 0.198 0.028
Substring (strstr) 0.057 1.273 1.030 0.088
Thread creation/ 0.248 0.126 45.761 0.142
joining
Mutex lock/unlock 0.042 0.055 0.785 0.046
UTF-8 decode 0.073 0.140 0.257 0.351
buffered
UTF-8 decode 0.153 0.395 0.236 0.563
byte-by-byte
Stdio putc/getc 0.270 0.808 7.791 0.497
Stdio putc/getc 0.200 0.282 0.269 0.144
unlocked
Regex compile 0.058 0.041 0.014 0.039
Regex search (a{25} 0.188 0.188 0.967 0.137
b)
Self-exec (static 234us 245us 272us 457us
linked)
Self-exec (dynamic 446us 590us 675us 864us
linked)
ABI and versioning musl uClibc dietlibc glibc
comparison
Stable ABI yes no unofficially yes
LSB-compatible ABI incomplete no no yes
Backwards yes no unofficially yes
compatibility
Forwards yes no unofficially no
compatibility
Atomic upgrades yes no no no
Symbol versioning no no no yes
Algorithms musl uClibc dietlibc glibc
comparison
Substring search twoway naive naive twoway
(strstr)
Regular expressions dfa dfa backtracking dfa
Sorting (qsort) smoothsort shellsort naive introsort
quicksort
Allocator (malloc) musl-native dlmalloc diet-native ptmalloc
Features comparison musl uClibc dietlibc glibc
Conformant printf yes yes no yes
Exact floating point yes no no yes
printing
C99 math library yes partial no yes
C11 threads API yes no no no
C11 thread-local yes yes no yes
storage
GCC libstdc++ yes yes no yes
compatibility
POSIX threads yes yes, on most broken yes
archs
POSIX process stub incorrect no incorrect
scheduling
POSIX thread yes yes no yes
priority scheduling
POSIX localedef no no no yes
Wide character yes yes minimal yes
interfaces
Legacy 8-bit no yes minimal slow, via
codepages gconv
Legacy CJK encodings no no no slow, via
gconv
native; 100% native; dangerously slow, via
UTF-8 multibyte conformant nonconformant nonconformant gconv;
nonconformant
Iconv character most major mainly UTFs no the kitchen
conversions encodings sink
Iconv
transliteration no no no yes
extension
Openwall-style TCB yes no no no
shadow
Sun RPC, NIS no yes yes yes
Zoneinfo (advanced yes no yes yes
timezones)
Gmon profiling no no yes yes
Debugging features no no no yes
Various Linux yes yes partial yes
extensions
Target architectures musl uClibc dietlibc glibc
comparison
i386 yes yes yes yes
x86_64 yes yes yes yes
x86_64 x32 ABI experimental no no non-conforming
(ILP32)
ARM yes yes yes yes
Aarch64 (64-bit ARM) yes no no yes
MIPS yes yes yes yes
SuperH yes yes no yes
Microblaze yes partial no yes
PowerPC (32- and yes yes yes yes
64-bit)
Sparc no yes yes yes
Alpha no yes yes yes
S/390 (32-bit) no no yes yes
S/390x (64-bit) yes no yes yes
OpenRISC 1000 (or1k) yes no no not upstream
Motorola 680x0 yes yes no yes
(m68k)
MMU-less yes, elf/ yes, bflt no no
microcontrollers fdpic
Build environment musl uClibc dietlibc glibc
comparison
Legacy-code-friendly partial yes no yes
headers
Lightweight headers yes no yes no
Usable without yes no yes no
native toolchain
Respect for C yes LFS64 no LFS64 problems
namespace problems
Respect for POSIX yes LFS64 no LFS64 problems
namespace problems
Security/hardening musl uClibc dietlibc glibc
comparison
Attention to corner yes yes no too much
cases malloc
Safe UTF-8 decoder yes yes no yes
Avoids superlinear yes sometimes no yes
big-O's
Stack smashing yes yes no yes
protection
Heap corruption yes no no yes
detection
Misc. comparisons musl uClibc dietlibc glibc
License MIT LGPL 2.1 GPL 2 LGPL 2.1+ w/
exceptions
Notes
In general
For each comparison in the table, each library is marked in red,
yellow, or green. Red or yellow indicates that the library fails to
support a feature or satisfy an optimality condition that may be
desirable to some users.
For comparisons involving testing and measurement, the particular
library versions compared are:
* musl 1.1.5
* uClibc 0.9.33.2 (Buildroot 2015.02)
* dietlibc 0.32
* glibc 2.19
Note that previous versions of this comparison included eglibc rather
than glibc, mainly since Debian-based distributions were using the
eglibc fork during the time in which glibc was essentially
unmaintained. Since most of eglibc has been merged back into glibc
and eglibc is being discontinued, the comparison has been updated
based on glibc.
Bloat comparison
Roughly speaking, "bloat" is used to refer to overhead cost that does
not contribute to the functioning of an application.
All figures are approximate based on the tests of versions of these
libraries available on systems I use. I've used size(1) instead of
file size since static library files are roughly 80% ELF header
overhead for the contained object files. Part of what makes the
shared libraries larger than their static equivalents is that they
include parts of libgcc for long division and other math functions.
The size totals for glibc include the size of iconv modules, roughly
5M, in the "Complete .so set" figure. These are essential to
providing certain functionality, and should be installed whether
static or dynamic linking is being used.
The smallest C program is:
int main() {}
And the "hello" program I used is:
#include
int main(int argc, char **argv) { printf("hello %d\n", argc); }
I've written it this way to ensure that the compiler cannot optimize
the string printed to a constant and replace the call to printf with
a call to puts.
Overhead is measured in dirty pages, i.e. the amount of swap-backed
physical memory each process requires. These are a mix of private
copy-on-write maps of the program image on disk, the heap, the stack,
and anonymous maps. The /proc/$pid/smaps file was used to obtain the
numbers for a program spinning in an infinite loop.
Dynamic linking overhead is largely dependent on the dynamic linker.
A good 12-16k of the dynamic overhead is due to inefficiency in the
standard dynamic linker. Ideally, replacing it could drop the
overhead difference between static- and dynamic-linked programs to a
single page.
It should be noted that uClibc was tested with many optional features
enabled, particularly locale. Due to a bug (design flaw) in uClibc's
locale support, locale loading code and malloc get linked even in
programs which never use setlocale.
Behavior on resource exhaustion
These comparions deal with the robstness of various interfaces when
the amount of free memory or other system resources are extremely
low. Reporting failure is shaded green when it is the theoretical
optimal behavior; it is shaded yellow when an alternate
implementation could successfully perform the operation with no
resource usage.
Thread-local storage covers both the case of attempting to create a
new thread when there is insufficient memory available to satisfy the
thread-local storage requirements of all loaded modules, and the case
of attempting to load a new module with thread-local storage via
dlopen when there is insufficient memory available to satisfy the
storage requirements of all extant threads.
In the case of pthread_cancel, NPTL dynamically loads libgcc_s.so.1
at runtime upon the first cancellation request, and aborts the
program if loading fails for any reason, including but not limited to
resource exhaustion.
Performance comparison
All of these figures were obtained using my libc-bench suite, in
UTF-8 locales, on one particular Intel Atom N280-based machine. They
are not intended to be rigorous, only to give a rough idea of
relative order-of-magnitude performance.
The tiny and big allocation figures are from b_malloc_tiny1 and
b_malloc_big1. The allocation contention tests measure malloc
performance when two threads are simultaneously performing allocation
and free operations. In the first test (local), each thread frees its
own allocations. In the second (shared), the allocating and freeing
thread are often not the same, breaking thread-local arena/cache
optimizations.
The strstr figure is the max time taken by any of the strstr tests,
in the interest of measuring worst-case time; which case is worst
varies by implementation. glibc's bad performance could be fixed
trivially by removing the code that disables the best optimization
for needles shorter than 32 bytes; with this change it should match
or slightly outperform musl.
The thread create and join figure is from
b_pthread_createjoin_serial1.
ABI and versioning comparison
Backwards compatibility means the usual thing, that new versions of
the library are compatible with programs compiled against an older
version. "Forwards compatibility" is a term I may have invented, but
the idea it's intended to convey is that old versions of the library
are compatible with programs compiled against a newer version, as
long as the program does not depend on features that were missing
from the older library version. In the latter case, the program would
simply fail at (static or dynamic) link time with missing symbols.
Perhaps the simplest way to think of "forwards compatibility" is that
it means you're not required to upgrade the library unless a program
actually needs functionality that's missing in your version.
Symbol versioning and forwards compatibility both have merits, but
they're essentially mutually exclusive.
"Atomic upgrades" means that a single atomic filesystem operation
upgrades the library, with no race condition window during which
dynamic-linked programs might fail to run. The canonical way to
ensure atomic upgrades is having the whole library in a single .so
file.
Algorithms comparison
When comparing substring search algorithms, m typically refers to the
length of the needle (substring) and n typically refers to the length
of the haystack (string to be searched). The two-way algorithm is O
(n), and with the Boyer-Moore-like improvements musl uses (and which
glibc uses, but only for extremely long needles), typical runtime is
proportional to n/m. The naive algorithm is O(nm).
Backtracking regular expression implementations are simple to write,
but have pathologically bad performance on many simile real-world
expressions, and fail to take advantage of the regularity of the
language.
The naive quicksort dietlibc uses has O(n) space requirement on the
stack, meaning it can and will lead to stack-overflow crashes in
real-world usage. This can be fixed by choosing the optimal order of
recursion and performing tail-call optimizations. Quicksort is also O
(n2) in time, and while typical performance is much better,
worst-case performance is very bad. Shell sort is typically O(n^a)
where 1