强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

musl 与 glibc 完全对比教程 / 第 09 章:性能对比分析

第 09 章:性能对比分析

通过基准测试深入了解 musl 与 glibc 在内存使用、启动时间、线程性能和 IO 性能方面的差异。

9.1 性能测试环境

测试平台

# 查看系统信息
$ uname -a
$ cat /proc/cpuinfo | head -20
$ free -h
$ cat /etc/os-release

# 测试环境(本章示例)
# CPU: Intel i7-12700K (12C/20T)
# RAM: 32 GB DDR4
# OS: Ubuntu 24.04 (glibc) + Alpine 3.20 (musl)
# Kernel: 6.5

编译环境

# glibc 编译
$ gcc --version | head -1
# gcc (Ubuntu 13.3.0-1ubuntu1) 13.3.0
$ ldd --version | head -1
# ldd (Ubuntu GLIBC 2.39-0ubuntu8) 2.39

# musl 编译
$ musl-gcc --version | head -1
# gcc (Alpine 13.2.1_git20240309) 13.2.1 20240309
$ echo "int main(){return 0;}" | musl-gcc -xc - -o /tmp/test && file /tmp/test
# ELF 64-bit LSB executable, dynamically linked, interpreter /lib/ld-musl-x86_64.so.1

9.2 内存使用对比

进程内存占用测试

/* mem_usage.c — 测量进程内存占用 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void print_meminfo(const char *label) {
    char path[64];
    snprintf(path, sizeof(path), "/proc/%d/status", getpid());

    FILE *fp = fopen(path, "r");
    char line[256];
    long vmrss = 0, vmhwm = 0;

    while (fgets(line, sizeof(line), fp)) {
        if (sscanf(line, "VmRSS: %ld kB", &vmrss) == 1) continue;
        if (sscanf(line, "VmHWM: %ld kB", &vmhwm) == 1) continue;
    }
    fclose(fp);

    printf("%-20s VmRSS: %6ld kB  VmHWM: %6ld kB\n", label, vmrss, vmhwm);
}

int main() {
    print_meminfo("Empty program");

    /* 分配 10 MB 内存 */
    char *buf = malloc(10 * 1024 * 1024);
    memset(buf, 0, 10 * 1024 * 1024);
    print_meminfo("After 10MB alloc");

    /* 分配 100 MB */
    char *buf2 = malloc(100 * 1024 * 1024);
    memset(buf2, 0, 100 * 1024 * 1024);
    print_meminfo("After 100MB alloc");

    free(buf);
    free(buf2);
    print_meminfo("After free");

    return 0;
}
# 编译运行
$ gcc -O2 -o mem_test_glibc mem_usage.c
$ musl-gcc -O2 -o mem_test_musl mem_usage.c

$ echo "=== glibc ===" && ./mem_test_glibc
=== glibc ===
Empty program        VmRSS:   1536 kB  VmHWM:   1536 kB
After 10MB alloc     VmRSS:  11776 kB  VmHWM:  11776 kB
After 100MB alloc    VmRSS: 112128 kB  VmHWM: 112128 kB
After free           VmRSS:   2048 kB  VmHWM: 112128 kB

$ echo "=== musl ===" && ./mem_test_musl
=== musl ===
Empty program        VmRSS:    512 kB  VmHWM:    512 kB
After 10MB alloc     VmRSS:  10752 kB  VmHWM:  10752 kB
After 100MB alloc    VmRSS: 111104 kB  VmHWM: 111104 kB
After free           VmRSS:    512 kB  VmHWM: 111104 kB

空进程内存占用

指标glibcmusl差异
空进程 VmRSS~1.5 MB~0.5 MBmusl 少 67%
libc 映射大小~2.5 MB~600 KBmusl 少 76%
TLS 开销~16 KB~8 KBmusl 少 50%
线程默认栈8 MB128 KBmusl 少 98%

线程内存开销

/* thread_mem.c — 测量线程内存开销 */
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define NUM_THREADS 1000

static void print_mem(const char *label) {
    char cmd[128];
    snprintf(cmd, sizeof(cmd),
             "grep VmRSS /proc/%d/status | awk '{print \"%s: \" $2 \" kB\"}'",
             getpid(), label);
    system(cmd);
}

void *thread_func(void *arg) {
    sleep(60);  /* 保持线程存活 */
    return NULL;
}

int main() {
    pthread_t tids[NUM_THREADS];
    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, 256 * 1024);  /* 256KB 栈 */

    print_mem("Before threads");

    for (int i = 0; i < NUM_THREADS; i++) {
        if (pthread_create(&tids[i], &attr, thread_func, NULL) != 0) {
            printf("Failed to create thread %d\n", i);
            break;
        }
    }

    print_mem("After 1000 threads");

    /* 清理 */
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_cancel(tids[i]);
        pthread_join(tids[i], NULL);
    }
    pthread_attr_destroy(&attr);

    print_mem("After cleanup");
    return 0;
}

/*
 * glibc(256KB 栈 x 1000 线程):~260 MB
 * musl(256KB 栈 x 1000 线程):~260 MB
 * 相同栈大小下,内存占用基本一致
 *
 * 但 musl 默认 128KB vs glibc 默认 8MB:
 * glibc:~8 GB(1000 线程 x 8MB 默认栈)
 * musl:~128 MB(1000 线程 x 128KB 默认栈)
 */

9.3 启动时间对比

程序启动基准测试

/* startup_bench.c — 测量程序启动时间 */
#include <stdio.h>
#include <time.h>

int main() {
    /* 程序启动到 main() 的时间由动态链接器决定 */
    /* 使用外部计时来测量 */
    return 0;
}
# 编译两种版本
$ gcc -O2 -o startup_glibc startup_bench.c
$ musl-gcc -O2 -o startup_musl startup_bench.c
$ musl-gcc -static -O2 -o startup_musl_static startup_bench.c

# 测量启动时间(使用 perf)
$ perf stat -r 100 ./startup_glibc 2>&1 | grep "time elapsed"
$ perf stat -r 100 ./startup_musl 2>&1 | grep "time elapsed"
$ perf stat -r 100 ./startup_musl_static 2>&1 | grep "time elapsed"

# 或者使用 hyperfine
$ hyperfine --warmup 10 \
    './startup_glibc' \
    './startup_musl' \
    './startup_musl_static'

# 典型结果:
# glibc 动态链接:  ~0.5 ms
# musl 动态链接:   ~0.3 ms
# musl 静态链接:   ~0.1 ms

复杂程序启动时间

# 测试复杂程序的启动时间
# 以 Python 为例

$ hyperfine --warmup 5 \
    'python3 -c "print(1)"' \
    'python3 -c "import sys; print(sys.version)"'

# 在 glibc (Ubuntu) 和 musl (Alpine) 上分别测试
# 差异通常很小(~5-10%),因为 Python 本身的初始化时间主导

Docker 容器启动时间

# 测量容器启动时间
$ time docker run --rm alpine:3.20 echo "hello"
# real    0m0.350s  (Alpine/musl)

$ time docker run --rm ubuntu:24.04 echo "hello"
# real    0m0.520s  (Ubuntu/glibc)

# 差异主要来自:
# 1. 镜像大小(alpine 更小,overlay fs 层更少)
# 2. 动态链接器加载(musl 更轻量)
# 3. init 进程(如有)

9.4 线程性能对比

线程创建速度

/* thread_create_bench.c */
#include <pthread.h>
#include <stdio.h>
#include <time.h>

#define ITERATIONS 100000

void *empty_thread(void *arg) {
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, 256 * 1024);

    struct timespec start, end;
    clock_gettime(CLOCK_MONOTONIC, &start);

    for (int i = 0; i < ITERATIONS; i++) {
        pthread_create(&tid, &attr, empty_thread, NULL);
        pthread_join(tid, NULL);
    }

    clock_gettime(CLOCK_MONOTONIC, &end);
    double elapsed = (end.tv_sec - start.tv_sec) +
                     (end.tv_nsec - start.tv_nsec) / 1e9;

    printf("Thread create+join x %d: %.3f sec (%.1f μs each)\n",
           ITERATIONS, elapsed, elapsed / ITERATIONS * 1e6);

    pthread_attr_destroy(&attr);
    return 0;
}
$ gcc -O2 -pthread -o thread_bench_glibc thread_create_bench.c
$ musl-gcc -O2 -pthread -o thread_bench_musl thread_create_bench.c

$ echo "=== glibc ===" && ./thread_bench_glibc
=== glibc ===
Thread create+join x 100000: 1.523 sec (15.2 μs each)

$ echo "=== musl ===" && ./thread_bench_musl
=== musl ===
Thread create+join x 100000: 1.087 sec (10.9 μs each)

# musl 线程创建通常快 20-30%

线程同步性能

/* mutex_bench.c — 互斥锁性能测试 */
#include <pthread.h>
#include <stdio.h>
#include <time.h>

#define NUM_THREADS 4
#define ITERATIONS 10000000

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
long counter = 0;

void *increment(void *arg) {
    for (int i = 0; i < ITERATIONS; i++) {
        pthread_mutex_lock(&mutex);
        counter++;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main() {
    pthread_t tids[NUM_THREADS];
    struct timespec start, end;

    clock_gettime(CLOCK_MONOTONIC, &start);

    for (int i = 0; i < NUM_THREADS; i++)
        pthread_create(&tids[i], NULL, increment, NULL);
    for (int i = 0; i < NUM_THREADS; i++)
        pthread_join(tids[i], NULL);

    clock_gettime(CLOCK_MONOTONIC, &end);
    double elapsed = (end.tv_sec - start.tv_sec) +
                     (end.tv_nsec - start.tv_nsec) / 1e9;

    printf("Mutex: %d threads x %d ops = %.3f sec (%.1f ns/op)\n",
           NUM_THREADS, ITERATIONS, elapsed,
           elapsed / (NUM_THREADS * ITERATIONS) * 1e9);
    return 0;
}

读写锁性能

/* rwlock_bench.c — 读写锁性能测试 */
#include <pthread.h>
#include <stdio.h>
#include <time.h>

#define NUM_READERS 8
#define NUM_WRITERS 2
#define ITERATIONS 1000000

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
long shared_data = 0;

void *reader(void *arg) {
    for (int i = 0; i < ITERATIONS; i++) {
        pthread_rwlock_rdlock(&rwlock);
        volatile long val = shared_data;
        (void)val;
        pthread_rwlock_unlock(&rwlock);
    }
    return NULL;
}

void *writer(void *arg) {
    for (int i = 0; i < ITERATIONS; i++) {
        pthread_rwlock_wrlock(&rwlock);
        shared_data++;
        pthread_rwlock_unlock(&rwlock);
    }
    return NULL;
}

线程性能总结

操作glibc NPTLmusl差异
pthread_create()~15 μs~10 μsmusl 快 ~30%
pthread_join()~2 μs~1.5 μsmusl 快 ~25%
pthread_mutex_lock/unlock~25 ns~30 nsglibc 快 ~17%
pthread_rwlock_rdlock~20 ns~25 nsglibc 快 ~20%
pthread_cond_wait/signal~50 ns~45 ns相当
条件变量广播O(n)O(n)相当

分析:musl 线程创建更快(栈分配更简单),但互斥锁操作 glibc 略快(futex 优化更成熟)。在实际应用中,这种差异通常被业务逻辑开销掩盖。


9.5 内存分配性能

malloc/free 性能测试

/* malloc_bench.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

static void bench_malloc(size_t obj_size, int count) {
    void **ptrs = malloc(count * sizeof(void *));
    struct timespec start, end;

    clock_gettime(CLOCK_MONOTONIC, &start);
    for (int i = 0; i < count; i++) {
        ptrs[i] = malloc(obj_size);
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    double alloc_time = (end.tv_sec - start.tv_sec) +
                        (end.tv_nsec - start.tv_nsec) / 1e9;

    clock_gettime(CLOCK_MONOTONIC, &start);
    for (int i = 0; i < count; i++) {
        free(ptrs[i]);
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    double free_time = (end.tv_sec - start.tv_sec) +
                       (end.tv_nsec - start.tv_nsec) / 1e9;

    printf("Size %6zu bytes: alloc %.3fs, free %.3fs (x%d)\n",
           obj_size, alloc_time, free_time, count);

    free(ptrs);
}

int main() {
    int count = 1000000;
    bench_malloc(16, count);      /* 小对象 */
    bench_malloc(64, count);      /* 小对象 */
    bench_malloc(256, count);     /* 中对象 */
    bench_malloc(4096, count);    /* 大对象 */
    bench_malloc(65536, count);   /* 大对象 */
    return 0;
}

多线程 malloc 性能

/* mt_malloc_bench.c — 多线程 malloc 压力测试 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <time.h>

#define NUM_THREADS 8
#define ALLOCS_PER_THREAD 500000

void *alloc_thread(void *arg) {
    void *ptrs[64];
    for (int i = 0; i < ALLOCS_PER_THREAD; i++) {
        int idx = i % 64;
        size_t size = 16 + (i % 1024);
        ptrs[idx] = malloc(size);
        if (ptrs[idx]) memset(ptrs[idx], 0, size);
        if (i >= 64) free(ptrs[(i - 64) % 64]);
    }
    for (int i = 0; i < 64; i++) free(ptrs[i]);
    return NULL;
}

int main() {
    pthread_t tids[NUM_THREADS];
    struct timespec start, end;

    clock_gettime(CLOCK_MONOTONIC, &start);
    for (int i = 0; i < NUM_THREADS; i++)
        pthread_create(&tids[i], NULL, alloc_thread, NULL);
    for (int i = 0; i < NUM_THREADS; i++)
        pthread_join(tids[i], NULL);
    clock_gettime(CLOCK_MONOTONIC, &end);

    double elapsed = (end.tv_sec - start.tv_sec) +
                     (end.tv_nsec - start.tv_nsec) / 1e9;
    printf("MT malloc: %d threads x %d allocs: %.3f sec\n",
           NUM_THREADS, ALLOCS_PER_THREAD, elapsed);
    return 0;
}

malloc 性能总结

场景glibc ptmallocmusl malloc差异
单线程小对象良好glibc 快 ~20%
单线程大对象良好良好相当(都用 mmap)
多线程并发快(per-thread arena)较慢(全局锁)glibc 快 2-5x
内存碎片较多较少musl 更优
内存归还⚠️ 可能不归还✅ 及时归还musl 更优
内存开销较高(arena 元数据)较低musl 更优

建议:如果程序大量并发 malloc/free,考虑使用 jemallocmimalloc,它们在两种 libc 上都有出色表现。


9.6 IO 性能对比

文件 IO 测试

/* io_bench.c */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>

#define FILE_SIZE (64 * 1024 * 1024)  /* 64 MB */
#define BLOCK_SIZE 4096

int main() {
    char *buf = malloc(FILE_SIZE);
    memset(buf, 'A', FILE_SIZE);
    const char *filename = "/tmp/io_bench_test";

    struct timespec start, end;

    /* 写入测试 */
    clock_gettime(CLOCK_MONOTONIC, &start);
    int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    for (size_t offset = 0; offset < FILE_SIZE; offset += BLOCK_SIZE) {
        write(fd, buf + offset, BLOCK_SIZE);
    }
    close(fd);
    clock_gettime(CLOCK_MONOTONIC, &end);
    double write_time = (end.tv_sec - start.tv_sec) +
                        (end.tv_nsec - start.tv_nsec) / 1e9;

    /* 读取测试 */
    clock_gettime(CLOCK_MONOTONIC, &start);
    fd = open(filename, O_RDONLY);
    for (size_t offset = 0; offset < FILE_SIZE; offset += BLOCK_SIZE) {
        read(fd, buf + offset, BLOCK_SIZE);
    }
    close(fd);
    clock_gettime(CLOCK_MONOTONIC, &end);
    double read_time = (end.tv_sec - start.tv_sec) +
                       (end.tv_nsec - start.tv_nsec) / 1e9;

    printf("Write 64MB: %.3f sec (%.1f MB/s)\n",
           write_time, FILE_SIZE / write_time / 1e6);
    printf("Read  64MB: %.3f sec (%.1f MB/s)\n",
           read_time, FILE_SIZE / read_time / 1e6);

    unlink(filename);
    free(buf);
    return 0;
}
# IO 性能在 glibc 和 musl 之间通常没有显著差异
# 因为两者都直接调用内核系统调用
# 差异主要在 stdio 缓冲区管理上

$ gcc -O2 -o io_bench_glibc io_bench.c && ./io_bench_glibc
$ musl-gcc -O2 -o io_bench_musl io_bench.c && ./io_bench_musl

# 典型结果:差异 < 5%

格式化 IO(printf)

/* printf_bench.c */
#include <stdio.h>
#include <time.h>

int main() {
    struct timespec start, end;
    const int iterations = 1000000;

    /* 输出到 /dev/null 以消除终端影响 */
    FILE *fp = fopen("/dev/null", "w");

    clock_gettime(CLOCK_MONOTONIC, &start);
    for (int i = 0; i < iterations; i++) {
        fprintf(fp, "Value: %d, Float: %.4f, String: %s, Hex: 0x%x\n",
                i, i * 3.14159, "hello world", i);
    }
    clock_gettime(CLOCK_MONOTONIC, &end);

    double elapsed = (end.tv_sec - start.tv_sec) +
                     (end.tv_nsec - start.tv_nsec) / 1e9;
    printf("fprintf x %d: %.3f sec (%.0f ns each)\n",
           iterations, elapsed, elapsed / iterations * 1e9);

    fclose(fp);
    return 0;
}

/*
 * printf 性能在两者之间差异很小
 * glibc 的 printf 实现更复杂(支持更多格式修饰符)
 * musl 的 printf 实现更简洁
 * 实际性能基本持平
 */

9.7 网络性能对比

Socket 性能

/* socket_bench.c — 基本 socket 性能测试 */
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>

#define MSG_SIZE 1024
#define ITERATIONS 100000

/* 网络性能主要取决于内核,而非 libc */
/* libc 仅是系统调用的薄封装 */

int main() {
    int sv[2];
    socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

    char send_buf[MSG_SIZE];
    char recv_buf[MSG_SIZE];
    memset(send_buf, 'A', MSG_SIZE);

    struct timespec start, end;
    clock_gettime(CLOCK_MONOTONIC, &start);

    for (int i = 0; i < ITERATIONS; i++) {
        write(sv[0], send_buf, MSG_SIZE);
        read(sv[1], recv_buf, MSG_SIZE);
    }

    clock_gettime(CLOCK_MONOTONIC, &end);
    double elapsed = (end.tv_sec - start.tv_sec) +
                     (end.tv_nsec - start.tv_nsec) / 1e9;

    printf("Socket pair: %d x %d byte msgs: %.3f sec (%.1f ns/msg)\n",
           ITERATIONS, MSG_SIZE, elapsed,
           elapsed / ITERATIONS * 1e9);

    close(sv[0]);
    close(sv[1]);
    return 0;
}

/*
 * 结论:socket 性能在 glibc 和 musl 之间几乎无差异
 * 因为两者都直接调用 send()/recv() 系统调用
 * 真正的性能瓶颈在内核网络栈
 */

9.8 综合基准测试总结

测试项glibcmusl差异说明
空进程 RSS1.5 MB0.5 MBmusl -67%libc 映射更小
线程创建15 μs10 μsmusl -33%栈分配更简单
mutex 操作25 ns30 nsglibc -17%futex 优化更成熟
单线程 malloc基准+20%glibc 更快ptmalloc 优化
多线程 malloc基准+2-5xglibc 快很多per-thread arena
memcpy 大块基准+30-50%glibc 更快AVX/ERMS 优化
strlen 长串基准+20-30%glibc 更快SIMD 优化
文件 IO基准~相同差异 < 5%系统调用主导
网络 IO基准~相同差异 < 5%系统调用主导
printf基准~相同差异 < 5%实现复杂度相当
程序启动(动态)基准-30%musl 更快链接器更轻量
程序启动(静态)N/A基准无动态链接开销musl 独有优势
内存碎片较多较少musl 更优malloc 算法简单

9.9 性能优化建议

glibc 优化技巧

# 1. 调整 malloc arena 数量
export MALLOC_ARENA_MAX=4  # 减少内存碎片

# 2. 启用 tcache(glibc 2.26+)
export MALLOC_TCACHE_COUNT=1024

# 3. 使用 HWCAP 选择最优函数
# glibc 自动完成,无需手动配置

# 4. 编译时指定 CPU 特性
$ gcc -O2 -march=native -mtune=native -o program program.c

musl 优化技巧

# 1. 使用静态链接消除动态链接开销
$ musl-gcc -static -O2 -o program program.c

# 2. 显式设置线程栈大小
$ export MUSL_LOCPATH=/usr/share/i18n/locales

# 3. 使用 jemalloc/mimalloc 替代内置 malloc
$ musl-gcc -O2 -o program program.c -ljemalloc

# 4. LTO 优化
$ musl-gcc -O2 -flto -o program program.c

9.10 本章小结

性能选择指南:

  • 内存受限:选择 musl(更小的 libc、更少的碎片)
  • 大量线程:选择 musl(更小的默认栈、更快的创建)
  • 高并发 malloc:选择 glibc 或外部 allocator
  • 计算密集:选择 glibc(更好的 SIMD 优化)
  • IO 密集:两者相当(瓶颈在内核)
  • 启动速度:选择 musl(更轻量的链接器)
  • 容器化:选择 musl(更小的镜像)

扩展阅读