强曰为道

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

07 - 系统集成

第 7 章:系统集成

7.1 集成方式概览

集成方式适用场景侵入性可控性
编译时链接自有 C/C++ 项目
LD_PRELOAD任意已有程序
编译软件时指定Redis、MySQL 等
语言运行时配置Go、PHP 等

7.2 C/C++ 项目集成

7.2.1 基本集成

// main.c
#include <stdio.h>
#include <stdlib.h>

// 方式 1:直接使用系统 malloc(配合 LD_PRELOAD)
// 方式 2:使用 jemalloc API
#ifdef USE_JEMALLOC
  #include <jemalloc/jemalloc.h>
  #define MALLOC(sz)      je_malloc(sz)
  #define FREE(p)         je_free(p)
  #define REALLOC(p, sz)  je_realloc(p, sz)
  #define CALLOC(n, sz)   je_calloc(n, sz)
#else
  #define MALLOC(sz)      malloc(sz)
  #define FREE(p)         free(p)
  #define REALLOC(p, sz)  realloc(p, sz)
  #define CALLOC(n, sz)   calloc(n, sz)
#endif

int main() {
    void *p = MALLOC(1024);
    FREE(p);
    return 0;
}
# 编译
gcc -O2 -DUSE_JEMALLOC -o myapp main.c -ljemalloc

7.2.2 CMake 集成

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyProject C)

find_package(PkgConfig REQUIRED)
pkg_check_modules(JEMALLOC REQUIRED IMPORTED_TARGET jemalloc)

add_executable(myapp main.c)
target_link_libraries(myapp PRIVATE PkgConfig::JEMALLOC)
target_compile_definitions(myapp PRIVATE USE_JEMALLOC)

7.2.3 Makefile 集成

CC = gcc
CFLAGS = -O2 -Wall -g
LDFLAGS = -ljemalloc -lpthread

# 如果 jemalloc 安装在非标准路径
JEMALLOC_PREFIX = /usr/local
CFLAGS  += -I$(JEMALLOC_PREFIX)/include
LDFLAGS += -L$(JEMALLOC_PREFIX)/lib -Wl,-rpath,$(JEMALLOC_PREFIX)/lib

myapp: main.c
	$(CC) $(CFLAGS) -DUSE_JEMALLOC -o $@ $< $(LDFLAGS)

clean:
	rm -f myapp

7.2.4 自定义 Allocator 封装

// my_allocator.h - 生产级封装
#ifndef MY_ALLOCATOR_H
#define MY_ALLOCATOR_H

#include <stddef.h>

#ifdef USE_JEMALLOC
  #include <jemalloc/jemalloc.h>

  static inline void *my_malloc(size_t sz)    { return je_malloc(sz); }
  static inline void  my_free(void *p)        { je_free(p); }
  static inline void *my_realloc(void *p, size_t sz) { return je_realloc(p, sz); }
  static inline void *my_calloc(size_t n, size_t sz) { return je_calloc(n, sz); }
  static inline size_t my_malloc_usable_size(void *p) { return je_malloc_usable_size(p); }

  static inline void my_malloc_stats(void) {
      je_malloc_stats_print(NULL, NULL, NULL);
  }

  static inline void my_get_stats(size_t *allocated, size_t *resident) {
      uint64_t epoch = 1; size_t sz = sizeof(epoch);
      je_mallctl("epoch", &epoch, &sz, &epoch, sz);
      sz = sizeof(size_t);
      if (allocated) je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
      if (resident)  je_mallctl("stats.resident",  resident,  &sz, NULL, 0);
  }

#else
  #include <stdlib.h>
  #include <malloc.h>

  static inline void *my_malloc(size_t sz)    { return malloc(sz); }
  static inline void  my_free(void *p)        { free(p); }
  static inline void *my_realloc(void *p, size_t sz) { return realloc(p, sz); }
  static inline void *my_calloc(size_t n, size_t sz) { return calloc(n, sz); }
  static inline size_t my_malloc_usable_size(void *p) { return malloc_usable_size(p); }
  static inline void my_malloc_stats(void) { /* no-op */ }
  static inline void my_get_stats(size_t *a, size_t *r) { if (a) *a = 0; if (r) *r = 0; }
#endif

#endif

7.3 Redis 集成

Redis 从 2.4.0 开始默认使用 jemalloc(Linux 版本)。

7.3.1 编译时指定

# Redis 默认使用 jemalloc(如果有)
make MALLOC=jemalloc

# 明确使用 jemalloc(而非 libc 或 tcmalloc)
make MALLOC=jemalloc CFLAGS="-g -O2"

# 使用自定义 jemalloc 路径
make MALLOC=jemalloc \
  JEMALLOC_PREFIX=/opt/jemalloc \
  CFLAGS="-I/opt/jemalloc/include" \
  LDFLAGS="-L/opt/jemalloc/lib -Wl,-rpath,/opt/jemalloc/lib"

7.3.2 运行时配置

# 启动 Redis 时配置 jemalloc
LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 \
MALLOC_CONF="narenas:8,dirty_decay_ms:3000,background_thread:true" \
redis-server /etc/redis/redis.conf

7.3.3 Redis 中的 jemalloc 命令

Redis 暴露了 jemalloc 的统计能力:

# Redis CLI 查看内存分配信息
redis-cli MEMORY STATS
redis-cli MEMORY DOCTOR
redis-cli MEMORY USAGE <key>
redis-cli INFO memory

# Redis 特有的 jemalloc 命令
redis-cli MEMORY MALLOC-STATS     # 等同于 je_malloc_stats_print()
redis-cli MEMORY PURGE            # 触发脏页回收

7.3.4 Redis 调优建议

参数推荐值说明
narenas4-8Redis 主要是单线程,不需要太多 Arena
dirty_decay_ms1000-5000及时归还内存
background_threadtrue避免主线程阻塞
tcache_max32768默认即可

7.4 MySQL / MariaDB 集成

7.4.1 编译时集成

# MariaDB 支持在编译时指定 jemalloc
cmake . \
  -DCMAKE_BUILD_TYPE=Release \
  -DWITH_JEMALLOC=ON \
  -DJEMALLOC_PREFIX=/usr/local
make -j$(nproc)
sudo make install

7.4.2 运行时集成

# systemd 服务配置
# /etc/systemd/system/mysql.service.d/jemalloc.conf
[Service]
Environment="LD_PRELOAD=/usr/local/lib/libjemalloc.so.2"
Environment="MALLOC_CONF=narenas:8,dirty_decay_ms:5000,background_thread:true"
sudo systemctl daemon-reload
sudo systemctl restart mysql

7.4.3 MySQL 中的 jemalloc 统计

-- MariaDB 查看内存分配器信息(如果编译时启用了 jemalloc)
SHOW ENGINE INNODB STATUS\G
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';

7.5 PHP 集成

7.5.1 PHP-FPM 使用 jemalloc

# 方法 1:LD_PRELOAD
# /etc/systemd/system/php-fpm.service.d/jemalloc.conf
[Service]
Environment="LD_PRELOAD=/usr/local/lib/libjemalloc.so.2"
Environment="MALLOC_CONF=narenas:4,dirty_decay_ms:2000"

7.5.2 编译 PHP 时使用 jemalloc

./configure \
  --prefix=/usr/local/php \
  --enable-fpm \
  LDFLAGS="-L/usr/local/lib -ljemalloc" \
  CFLAGS="-I/usr/local/include"
make -j$(nproc) && sudo make install

7.6 Go 项目集成

Go 语言使用自己的内存分配器(基于 tcmalloc 思路),通常不直接使用 jemalloc。但在调用 C 库(CGO)时,C 代码的内存分配可以使用 jemalloc。

7.6.1 CGO 中使用 jemalloc

package main

/*
#cgo LDFLAGS: -ljemalloc
#include <jemalloc/jemalloc.h>
#include <stdlib.h>

void* alloc_with_jemalloc(size_t size) {
    return je_malloc(size);
}

void dealloc_with_jemalloc(void* ptr) {
    je_free(ptr);
}
*/
import "C"
import "unsafe"

func AllocWithJemalloc(size int) unsafe.Pointer {
    return C.alloc_with_jemalloc(C.size_t(size))
}

func DeallocWithJemalloc(ptr unsafe.Pointer) {
    C.dealloc_with_jemalloc(ptr)
}

func main() {
    ptr := AllocWithJemalloc(1024)
    defer DeallocWithJemalloc(ptr)
    // 使用内存...
}
# 设置 CGO 编译路径
export CGO_CFLAGS="-I/usr/local/include"
export CGO_LDFLAGS="-L/usr/local/lib -ljemalloc"
go build -o myapp main.go

注意:Go 的 GC 管理的内存不经过 jemalloc,只有 C 代码分配的内存使用 jemalloc。


7.7 Python 中使用 jemalloc

7.7.1 LD_PRELOAD 方式

# 直接对 Python 进程使用 jemalloc
LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 python3 my_script.py

# 配合参数
LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 \
MALLOC_CONF="narenas:4,dirty_decay_ms:3000" \
python3 my_script.py

7.7.2 在代码中使用

# 方式 1:通过 ctypes 使用 jemalloc API
import ctypes
jemalloc = ctypes.CDLL("libjemalloc.so.2")

# 使用 jemalloc 的 malloc 和 free
ptr = jemalloc.je_malloc(1024)
jemalloc.je_free(ptr)

# 方式 2:全局 LD_PRELOAD 即可,无需代码改动

7.8 Rust 中使用 jemalloc

7.8.1 使用 jemallocator crate

# Cargo.toml
[dependencies]
jemallocator = "0.5"
// src/main.rs
#[global_allocator]
static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;

fn main() {
    // 所有 Rust 内存分配自动使用 jemalloc
    let v = vec![0u8; 1024 * 1024];
    println!("allocated {} bytes", v.len());
}

7.8.2 使用 tikv-jemallocator(维护更活跃)

# Cargo.toml
[dependencies]
tikv-jemallocator = "0.6"
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;

7.9 Nginx 集成

# 编译 Nginx 时指定 jemalloc
./configure \
  --prefix=/etc/nginx \
  --with-ld-opt="-L/usr/local/lib -ljemalloc" \
  --with-cc-opt="-I/usr/local/include"
make -j$(nproc) && sudo make install

或使用 LD_PRELOAD:

# /etc/systemd/system/nginx.service.d/jemalloc.conf
[Service]
Environment="LD_PRELOAD=/usr/local/lib/libjemalloc.so.2"
Environment="MALLOC_CONF=narenas:4,dirty_decay_ms:3000,background_thread:true"

7.10 集成注意事项

要点说明
malloc/free 必须匹配jemalloc 分配的内存必须用 jemalloc 的 free 释放
LD_PRELOAD 全局生效所有共享库的 malloc 都会被替换
自定义前缀共存使用 --with-jemalloc-prefix 实现精确控制
调试时禁用ASan、Valgrind 等工具与 jemalloc 冲突
容器中使用确保库文件在容器内可访问

7.11 本章小结

软件集成方式推荐配置
Redis默认内置narenas:4-8,background_thread:true
MySQL/MariaDB编译选项或 LD_PRELOADnarenas:8,dirty_decay_ms:5000
PHP-FPMLD_PRELOADnarenas:4,dirty_decay_ms:2000
Go (CGO)CGO LDFLAGS按需配置
Rustjemallocator crate默认即可
Nginxld-opt 或 LD_PRELOADnarenas:4,background_thread:true

扩展阅读

  1. Redis 内存优化
  2. Rust jemallocator
  3. MariaDB jemalloc 支持

上一章第 6 章:性能调优 下一章第 8 章:基准测试