CPU亲和性与NUMA优化实战
深入解析CPU亲和性原理、NUMA架构特性及其在高性能系统中的应用实践
在现代多核服务器系统中,CPU亲和性(CPU Affinity)和NUMA(Non-Uniform Memory Access)架构的优化对于提升系统性能至关重要。本文将深入探讨CPU亲和性的原理和配置方法,分析NUMA架构对内存访问性能的影响,并提供针对性的优化策略。
CPU亲和性原理
CPU亲和性是指将进程或线程绑定到特定的CPU核心上运行的技术,通过这种方式可以减少进程在不同核心间迁移的开销,提高缓存命中率。
亲和性的性能优势
Rendering diagram...
CPU亲和性的主要性能优势体现在:
- 缓存复用:进程持续在相同核心上运行,可以充分利用CPU各级缓存
- TLB效率:减少TLB(Translation Lookaside Buffer)失效,提高地址转换效率
- NUMA友好:避免进程在NUMA节点间迁移,减少远程内存访问延迟
亲和性配置方法
Linux提供了多种方式来设置CPU亲和性:
#include <sched.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// 设置进程CPU亲和性
int set_process_affinity(pid_t pid,int cpu_core) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_core,&cpuset);
return sched_setaffinity(pid,sizeof(cpu_set_t),&cpuset);
}
// 设置线程CPU亲和性
int set_thread_affinity(pthread_t thread,int cpu_core) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_core,&cpuset);
return pthread_setaffinity_np(thread,sizeof(cpu_set_t),&cpuset);
}
// 获取当前CPU亲和性设置
void get_current_affinity() {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
if (sched_getaffinity(0,sizeof(cpu_set_t),&cpuset) == 0) {
printf("当前进程可运行的CPU核心: ");
for (int i = 0; i < CPU_SETSIZE; i++) {
if (CPU_ISSET(i,&cpuset)) {
printf("%d ",i);
}
}
printf("\n");
}
}
int main() {
// 设置当前进程在CPU核心0上运行
if (set_process_affinity(getpid(),0) != 0) {
perror("设置CPU亲和性失败");
return 1;
}
get_current_affinity();
// 创建多个线程并设置不同的亲和性
pthread_t threads[4];
for (int i = 0; i < 4; i++) {
pthread_create(&threads[i],NULL,NULL,NULL);
set_thread_affinity(threads[i],i);
}
// 主循环
while (1) {
// 在绑定的CPU核心上执行计算密集型任务
volatile double result = 0;
for (int i = 0; i < 1000000; i++) {
result += i * 0.001;
}
usleep(1000);
}
return 0;
}
命令行工具设置亲和性
使用taskset命令可以方便地设置进程的CPU亲和性:
# 设置进程在CPU核心0,2,4,6上运行
taskset -c 0,2,4,6 ./your_program
# 查看进程的CPU亲和性设置
taskset -cp <pid>
# 使用十六进制掩码设置(二进制1111对应核心0-3)
taskset -p 0xf <pid>
# 启动时指定CPU核心
taskset -c 0-3 ./your_program
性能测试对比
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/time.h>
#include <pthread.h>
#define NUM_ITERATIONS 10000000
// 计算密集型任务
void compute_task() {
volatile double sum = 0.0;
for (int i = 0; i < NUM_ITERATIONS; i++) {
sum += i * 0.001;
}
}
// 性能测试函数
void benchmark_with_affinity(int use_affinity) {
struct timeval start,end;
gettimeofday(&start,NULL);
if (use_affinity) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0,&cpuset);
sched_setaffinity(0,sizeof(cpu_set_t),&cpuset);
printf("使用CPU亲和性绑定核心0\n");
} else {
printf("不使用CPU亲和性\n");
}
compute_task();
gettimeofday(&end,NULL);
double elapsed = (end.tv_sec - start.tv_sec) +
(end.tv_usec - start.tv_usec) / 1000000.0;
printf("执行时间: %.4f 秒\n",elapsed);
}
int main() {
printf("CPU亲和性性能测试\n");
printf("===================================\n");
benchmark_with_affinity(0);
printf("\n");
benchmark_with_affinity(1);
return 0;
}
NUMA架构深入分析
NUMA(Non-Uniform Memory Access)是一种多处理器系统架构,其中每个处理器都有本地内存,访问本地内存的速度比访问其他处理器的内存(远程内存)更快。
NUMA架构原理
Rendering diagram...
NUMA性能差异测试
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <numa.h>
#include <sys/time.h>
#include <string.h>
#define ARRAY_SIZE (256 * 1024 * 1024) // 1GB数组
#define NUM_ITERATIONS 100
// NUMA性能测试
void numa_performance_test() {
if (!numa_available()) {
printf("NUMA不可用\n");
return;
}
int max_node = numa_max_node();
printf("NUMA节点数: %d\n",max_node + 1);
// 在节点0上分配内存
void *local_mem = numa_alloc_onnode(ARRAY_SIZE,0);
memset(local_mem,0,ARRAY_SIZE);
// 在节点1上分配内存
void *remote_mem = numa_alloc_onnode(ARRAY_SIZE,1);
memset(remote_mem,0,ARRAY_SIZE);
// 绑定当前进程到节点0
numa_run_on_node(0);
numa_set_preferred(0);
struct timeval start,end;
volatile int sum = 0;
// 测试本地内存访问
printf("测试本地内存访问 (Node 0)...\n");
gettimeofday(&start,NULL);
for (int iter = 0; iter < NUM_ITERATIONS; iter++) {
int *array = (int *)local_mem;
for (int i = 0; i < ARRAY_SIZE / sizeof(int); i += 64) {
sum += array[i];
}
}
gettimeofday(&end,NULL);
double local_time = (end.tv_sec - start.tv_sec) +
(end.tv_usec - start.tv_usec) / 1000000.0;
printf("本地内存访问时间: %.4f 秒\n",local_time);
// 测试远程内存访问
printf("测试远程内存访问 (Node 1)...\n");
gettimeofday(&start,NULL);
for (int iter = 0; iter < NUM_ITERATIONS; iter++) {
int *array = (int *)remote_mem;
for (int i = 0; i < ARRAY_SIZE / sizeof(int); i += 64) {
sum += array[i];
}
}
gettimeofday(&end,NULL);
double remote_time = (end.tv_sec - start.tv_sec) +
(end.tv_usec - start.tv_usec) / 1000000.0;
printf("远程内存访问时间: %.4f 秒\n",remote_time);
printf("性能差异: %.2f%%\n",(remote_time - local_time) / local_time * 100);
// 释放内存
numa_free(local_mem,ARRAY_SIZE);
numa_free(remote_mem,ARRAY_SIZE);
}
int main() {
numa_performance_test();
return 0;
}
NUMA配置与监控
# 查看NUMA拓扑结构
numactl --hardware
# 查看NUMA内存统计
numastat
# 查看进程NUMA状态
numastat -p <pid>
# 查看内存节点分布
lstopo-no-graphics
# 设置进程NUMA策略
numactl --cpunodebind=0 --membind=0 ./your_program
# 在特定节点上分配内存
numactl --preferred=1 ./your_program
# 查看NUMA内存使用情况
cat /sys/devices/system/node/node0/meminfo
NUMA感知的内存分配策略
内核NUMA内存分配策略
Rendering diagram...
应用层NUMA优化
#include <numa.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
// NUMA感知的内存池
typedef struct {
void **blocks;
size_t block_size;
size_t block_count;
int numa_node;
pthread_mutex_t mutex;
} numa_memory_pool;
// 创建NUMA感知的内存池
numa_memory_pool* create_numa_pool(size_t block_size,size_t block_count,int numa_node) {
numa_memory_pool *pool = malloc(sizeof(numa_memory_pool));
if (!pool) return NULL;
pool->blocks = malloc(block_count * sizeof(void *));
pool->block_size = block_size;
pool->block_count = block_count;
pool->numa_node = numa_node;
pthread_mutex_init(&pool->mutex,NULL);
// 在指定NUMA节点上分配内存
for (size_t i = 0; i < block_count; i++) {
pool->blocks[i] = numa_alloc_onnode(block_size,numa_node);
if (!pool->blocks[i]) {
// 清理已分配的内存
for (size_t j = 0; j < i; j++) {
numa_free(pool->blocks[j],block_size);
}
free(pool->blocks);
free(pool);
return NULL;
}
}
printf("NUMA内存池创建成功: 节点%d,%zu个块,每块%zu字节\n",
numa_node,block_count,block_size);
return pool;
}
// 从NUMA内存池分配内存
void* numa_pool_alloc(numa_memory_pool *pool) {
pthread_mutex_lock(&pool->mutex);
for (size_t i = 0; i < pool->block_count; i++) {
if (pool->blocks[i] != NULL) {
void *block = pool->blocks[i];
pool->blocks[i] = NULL;
pthread_mutex_unlock(&pool->mutex);
return block;
}
}
pthread_mutex_unlock(&pool->mutex);
return NULL;
}
// 释放内存到NUMA内存池
void numa_pool_free(numa_memory_pool *pool,void *block) {
pthread_mutex_lock(&pool->mutex);
for (size_t i = 0; i < pool->block_count; i++) {
if (pool->blocks[i] == NULL) {
pool->blocks[i] = block;
pthread_mutex_unlock(&pool->mutex);
return;
}
}
pthread_mutex_unlock(&pool->mutex);
numa_free(block,pool->block_size);
}
// 销毁NUMA内存池
void destroy_numa_pool(numa_memory_pool *pool) {
for (size_t i = 0; i < pool->block_count; i++) {
if (pool->blocks[i] != NULL) {
numa_free(pool->blocks[i],pool->block_size);
}
}
pthread_mutex_destroy(&pool->mutex);
free(pool->blocks);
free(pool);
}
// NUMA感知的数据结构
typedef struct {
int **data_rows;
int rows;
int cols;
int *row_nodes; // 记录每行所在的NUMA节点
} numa_matrix;
// 创建NUMA感知的矩阵
numa_matrix* create_numa_matrix(int rows,int cols) {
numa_matrix *matrix = malloc(sizeof(numa_matrix));
matrix->rows = rows;
matrix->cols = cols;
matrix->data_rows = malloc(rows * sizeof(int *));
matrix->row_nodes = malloc(rows * sizeof(int));
int num_nodes = numa_num_configured_nodes();
// 将行分配到不同的NUMA节点
for (int i = 0; i < rows; i++) {
int node = i % num_nodes;
matrix->data_rows[i] = numa_alloc_onnode(cols * sizeof(int),node);
matrix->row_nodes[i] = node;
// 初始化数据
for (int j = 0; j < cols; j++) {
matrix->data_rows[i][j] = i * cols + j;
}
}
printf("NUMA矩阵创建完成: %dx%d,分布在%d个节点\n",
rows,cols,num_nodes);
return matrix;
}
// NUMA感知的矩阵乘法
void numa_matrix_multiply(numa_matrix *a,numa_matrix *b,numa_matrix *result) {
for (int i = 0; i < a->rows; i++) {
for (int j = 0; j < b->cols; j++) {
int sum = 0;
for (int k = 0; k < a->cols; k++) {
sum += a->data_rows[i][k] * b->data_rows[k][j];
}
result->data_rows[i][j] = sum;
}
}
}
int main() {
if (!numa_available()) {
printf("NUMA不可用,需要支持NUMA的系统\n");
return 1;
}
// 创建NUMA内存池
numa_memory_pool *pool = create_numa_pool(4096,1000,0);
if (pool) {
void *block = numa_pool_alloc(pool);
if (block) {
printf("成功从NUMA内存池分配内存: %p\n",block);
numa_pool_free(pool,block);
}
destroy_numa_pool(pool);
}
// 创建NUMA感知的矩阵
numa_matrix *matrix = create_numa_matrix(100,100);
if (matrix) {
// 可以使用矩阵进行计算...
// 清理
for (int i = 0; i < matrix->rows; i++) {
numa_free(matrix->data_rows[i],matrix->cols * sizeof(int));
}
free(matrix->data_rows);
free(matrix->row_nodes);
free(matrix);
}
return 0;
}
性能优化实战案例
数据库NUMA优化
# MySQL NUMA优化配置
# 1. 禁用NUMA自动平衡
echo 0 > /proc/sys/kernel/numa_balancing
# 2. 使用numactl启动MySQL
numactl --cpunodebind=0 --membind=0 mysqld
# 3. 设置内存分配策略
numactl --interleave=all mysqld
# 4. 监控NUMA性能
numastat -p $(pidof mysqld)
watch -n 1 'numastat -p $(pidof mysqld)'
高性能网络应用优化
#include <pthread.h>
#include <numa.h>
#include <stdio.h>
#define NUM_WORKERS 4
// 工作线程参数
typedef struct {
int worker_id;
int numa_node;
pthread_t thread;
cpu_set_t cpuset;
} worker_param;
// 工作线程函数
void* worker_thread(void *arg) {
worker_param *param = (worker_param *)arg;
// 设置CPU亲和性
pthread_setaffinity_np(pthread_self(),sizeof(cpu_set_t),¶m->cpuset);
// 设置NUMA内存策略
numa_set_preferred(param->numa_node);
numa_run_on_node(param->numa_node);
printf("Worker %d 在节点%d 上启动\n",param->worker_id,param->numa_node);
// 分配NUMA本地内存
size_t buffer_size = 1024 * 1024; // 1MB
void *buffer = numa_alloc_local(buffer_size);
// 模拟网络处理工作
while (1) {
// 处理网络数据包
// 使用本地内存进行零拷贝处理
usleep(1000);
}
numa_free(buffer,buffer_size);
return NULL;
}
int main() {
if (!numa_available()) {
printf("NUMA不可用\n");
return 1;
}
int num_nodes = numa_num_configured_nodes();
printf("NUMA节点数: %d\n",num_nodes);
worker_param workers[NUM_WORKERS];
// 创建工作线程
for (int i = 0; i < NUM_WORKERS; i++) {
workers[i].worker_id = i;
workers[i].numa_node = i % num_nodes;
// 设置CPU亲和性
CPU_ZERO(&workers[i].cpuset);
int cpu = i;
CPU_SET(cpu,&workers[i].cpuset);
pthread_create(&workers[i].thread,NULL,worker_thread,&workers[i]);
}
// 等待工作线程
for (int i = 0; i < NUM_WORKERS; i++) {
pthread_join(workers[i].thread,NULL);
}
return 0;
}
JVM应用NUMA优化
# Java应用NUMA优化启动参数
java -XX:+UseNUMA \
-XX:+UseParallelGC \
-XX:ParallelGCThreads=8 \
-XX:+UseLargePages \
-Xms16g -Xmx16g \
-jar your_application.jar
# 使用numactl启动JVM
numactl --cpunodebind=0,1,2,3 --membind=0,1 \
java -XX:+UseNUMA -XX:+UseParallelGC \
-Xms16g -Xmx16g -jar your_application.jar
# 监控JVM NUMA性能
jstat -gc <pid> 1000
jinfo -flag UseNUMA <pid>
监控与诊断工具
NUMA性能监控
# 实时监控NUMA性能
watch -n 1 'cat /proc/vmstat | grep numa_'
# 查看NUMA内存使用情况
numastat -m
# 监控进程NUMA状态
numastat -p <pid>
# 查看NUMA节点详细信息
lscpu | grep -i numa
# 查看内存页面分布
cat /proc/<pid>/numa_maps
CPU亲和性监控
# 查看进程CPU亲和性
taskset -cp <pid>
# 查看线程CPU使用情况
ps -eo pid,tid,psr,comm | grep <process_name>
# 监控CPU调度信息
perf sched record -- sleep 10
perf sched report
# 查看上下文切换统计
vmstat 1
pidstat -w 1
性能分析脚本
#!/bin/bash
# NUMA性能诊断脚本
echo "NUMA系统信息"
echo "=========================="
numactl --hardware
echo -e "\nNUMA内存统计"
echo "=========================="
numastat
echo -e "\n当前进程NUMA状态"
echo "=========================="
for pid in $(pgrep -d ' ' your_process); do
echo "PID: $pid"
numastat -p $pid
taskset -cp $pid
done
echo -e "\nNUMA平衡状态"
echo "=========================="
cat /proc/sys/kernel/numa_balancing
cat /proc/vmstat | grep numa_
echo -e "\nCPU调度信息"
echo "=========================="
for pid in $(pgrep -d ' ' your_process); do
echo "PID: $pid 线程CPU分布:"
ps -eo pid,tid,psr,comm | awk -v pid=$pid '$1==pid {print $2,$3,$4}' | \
awk '{cpu_count[$2]++} END {for (cpu in cpu_count) print "CPU",cpu,":",cpu_count[cpu],"threads"}'
done
优化最佳实践
-
CPU亲和性设置原则
- 为计算密集型任务设置固定CPU亲和性
- 避免频繁修改进程的CPU亲和性
- 考虑超线程(SMT)的影响,优先使用物理核心
-
NUMA内存分配策略
- 优先使用本地内存,避免跨NUMA节点访问
- 大内存应用考虑使用内存池预分配
- 数据结构设计考虑NUMA拓扑结构
-
性能监控与调优
- 定期监控NUMA内存访问统计
- 使用性能分析工具识别热点
- 根据实际负载动态调整策略
-
系统级别优化
- 禁用不必要的NUMA自动平衡
- 合理配置大页内存(Huge Pages)
- 优化系统BIOS设置(如内存交错)
通过合理配置CPU亲和性和优化NUMA内存访问,可以显著提升多核服务器系统的性能,特别是在数据库、高性能计算和网络服务等对内存访问延迟敏感的应用中。