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