AWS Spot实例多核心绑定运行程序性能超100%下降求助
AWS Spot实例多核心绑定性能暴跌的原因分析与优化方案
问题与测试观察
- 核心问题:在AWS Spot实例中,同一程序绑定所有核心运行时,性能比绑定单个核心下降幅度超100%
- 测试数据:m7i.8xlarge(禁用超线程)实例上,单核心执行时间17秒,绑定全部16核心时平均执行时间35秒;该现象覆盖m7a.8xlarge、m6i.8xlarge、m6a.8xlarge、c7i.8xlarge、c7a.8xlarge、c6i.8xlarge、c6a.8xlarge、r7i.8xlarge、r7a.8xlarge、r6i.8xlarge、r6a.8xlarge等多款Spot实例
- 本地对比:禁用超线程的本地机器上,多核心绑定性能下降最高仅20%
测试程序
#include <stdio.h> #include <stdlib.h> #include <time.h> #define SIZE 1500 void initialize_matrix(double **matrix) { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { matrix[i][j] = (double)rand() / RAND_MAX; } } } void multiply_matrices(double **a, double **b, double **result) { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { result[i][j] = 0.0; for (int k = 0; k < SIZE; k++) { result[i][j] += a[i][k] * b[k][j]; } } } } int main() { double **a = (double **)malloc(SIZE * sizeof(double *)); double **b = (double **)malloc(SIZE * sizeof(double *)); double **result = (double **)malloc(SIZE * sizeof(double *)); for (int i = 0; i < SIZE; i++) { a[i] = (double *)malloc(SIZE * sizeof(double)); b[i] = (double *)malloc(SIZE * sizeof(double)); result[i] = (double *)malloc(SIZE * sizeof(double)); } srand(time(NULL)); initialize_matrix(a); initialize_matrix(b); clock_t start = clock(); multiply_matrices(a, b, result); clock_t end = clock(); double time_taken = (double)(end - start) / CLOCKS_PER_SEC; printf("Time taken for matrix multiplication: %f seconds\n", time_taken); for (int i = 0; i < SIZE; i++) { free(a[i]); free(b[i]); free(result[i]); } free(a); free(b); free(result); return 0; }
原因分析
- 程序无并行化,多核心绑定无意义且添开销:当前代码是纯单线程实现,绑定多核心时,操作系统会尝试将单线程调度到不同核心,不仅无法利用多核心算力,反而会因核心间上下文切换、缓存失效(线程迁移时需重新加载缓存数据)产生额外开销。AWS Spot实例的CPU调度机制更激进,导致该开销被放大。
- Spot实例的硬件资源共享特性:Spot实例依托AWS闲置物理机资源,即使禁用超线程,底层物理机仍可能有其他租户实例共享内存带宽、L3缓存等硬件资源。绑定所有核心时,程序占用更多共享资源,触发资源竞争,导致性能骤降;而本地机器为独占资源,竞争极少,性能下降幅度可控。
- 非连续内存布局导致缓存效率极低:代码使用二维指针数组(
double **),内存为非连续分布,缓存命中率极低。多核心调度时,每个核心的缓存需频繁加载分散的内存块,加上Spot实例的内存带宽可能因共享被限制,进一步恶化性能;本地机器内存带宽独占,缓存效率的影响相对较小。 - 时间统计方式的偏差:
clock()统计的是CPU时间而非墙上时间,单线程时CPU时间接近墙上时间;但多核心绑定单线程时,操作系统可能将线程调度到多个核心,导致CPU时间累积了多核心的调度时长,统计出的时间被高估。
优化方案
- 实现多线程并行计算:使用OpenMP、pthread等框架对矩阵乘法并行化,让每个核心处理独立计算任务,真正利用多核心算力。例如给
multiply_matrices添加OpenMP指令:
#include <omp.h> void multiply_matrices(double **a, double **b, double **result) { #pragma omp parallel for collapse(2) for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { result[i][j] = 0.0; for (int k = 0; k < SIZE; k++) { result[i][j] += a[i][k] * b[k][j]; } } } }
编译时需添加-fopenmp参数启用并行支持。
- 优化内存布局为连续内存:将二维指针数组改为一维连续数组,大幅提升缓存命中率。修改后的初始化与乘法函数示例:
void initialize_matrix(double *matrix) { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { matrix[i*SIZE + j] = (double)rand() / RAND_MAX; } } } void multiply_matrices(double *a, double *b, double *result) { #pragma omp parallel for collapse(2) for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { double sum = 0.0; for (int k = 0; k < SIZE; k++) { sum += a[i*SIZE + k] * b[k*SIZE + j]; } result[i*SIZE + j] = sum; } } }
避免无意义的多核心绑定:单线程程序无需绑定多核心,直接让操作系统默认调度,或绑定到单个核心,避免核心迁移带来的额外开销。
选用资源独占型实例(可选):若需稳定性能,可考虑AWS Dedicated Host或Dedicated Instance,确保实例独占物理机资源,避免共享带来的性能波动,不过这类实例成本高于Spot,需权衡成本与性能需求。
使用精准的时间统计方式:替换
clock()为clock_gettime(CLOCK_MONOTONIC)统计墙上时间,避免CPU时间统计的偏差:
#include <time.h> // ... struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); multiply_matrices(a, b, result); clock_gettime(CLOCK_MONOTONIC, &end); double time_taken = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9; printf("Time taken for matrix multiplication: %f seconds\n", time_taken);
内容的提问来源于stack exchange,提问作者Abhijeet Singh
相关产品推荐
相关产品推荐

