教程: 优化伪共享

本文更新于 2018.10.11

本文演示了如何通过Memory Access分析来发现伪共享问题(False Sharing), 示例代码中2个线程访问同一cache line的不同数据导致了此问题.

参考: https://software.intel.com/en-us/vtune-memory-access-tutorial-linux-pdf

工作流:

../../_images/tutorial_false_sharing.png

编译示例程序

解压linear_regression_vtune_amp_xe.tgz, 进入主目录, 运行make.

注解

此示例源码包仅包含在vtune 2018中.

创建vtune工程并运行分析

  • 配置要分析的可执行程序和运行参数, 这里参数为源码解压目录下的key_file.txt
  • 配置分析类型为Microarchitecture分组下的Memory Access, 并勾选”Analyze dynamic memory objects”, 设置”Minimal dynamic memory object size to track, in byptes”为1
../../_images/tutorial_false_sharing_2.png

完成后点击开始按钮开始分析.

注解

进行分析前最好通过BIOS设置关闭超线程支持, 否则对性能数据收集有影响. 性能分析工作完成后记得改回来

分析代码

从概要信息可知内存访问影响了性能:

../../_images/tutorial_false_sharing_3.png

下拉概要信息页面, 会看到”Top Memory Objects by Latency”, 这里给出了访问最慢的内存对象:

../../_images/tutorial_false_sharing_4.png

其中linear_regression_pthread.c的第136行代码如下:

tid_args = (lreg_args *)calloc(sizeof(lreg_args), num_procs);

它动态分配了一块内存, 用于每个线程函数的参数. 此处的内存会被每个线程访问, 比如:

// ADD UP RESULTS
for (i = 0; i < args->num_elems; i++)
{
    //Compute SX, SY, SYY, SXX, SXY
    args->SX  += args->points[i].x;
    args->SXX += args->points[i].x*args->points[i].x;
    args->SY  += args->points[i].y;
    args->SYY += args->points[i].y*args->points[i].y;
    args->SXY += args->points[i].x*args->points[i].y;
}

结构体lreg_args的定义如下:

typedef struct
{
    pthread_t tid;
    POINT_T *points;
    int num_elems;
    long long SX;
    long long SY;
    long long SXX;
    long long SYY;
    long long SXY;
} lreg_args;

调试代码可知此结构大小(sizeof)为64字节, 正好占用一个cache line, 然而由于多个lreg_args结构体在堆上动态分配, 无法保证以64字节对齐, 那么数组元素可能会被跨cache line访问, 造成false sharing问题.

优化代码

修改lreg_args结构体定义:

typedef struct
{
    char pad[80];
    pthread_t tid;
    POINT_T *points;
    int num_elems;
    long long SX;
    long long SY;
    long long SXX;
    long long SYY;
    long long SXY;
} lreg_args;

修改完成后重新编译分析, 可以看到Memory Bound大幅下降, false sharing消除.