为什么_mm_stream_ps会产生L1/LL缓存失误?[英] Why does _mm_stream_ps produce L1/LL cache misses?

本文是小编为大家收集整理的关于为什么_mm_stream_ps会产生L1/LL缓存失误?的处理方法,想解了为什么_mm_stream_ps会产生L1/LL缓存失误?的问题怎么解决?为什么_mm_stream_ps会产生L1/LL缓存失误?问题的解决办法?那么可以参考本文帮助大家快速定位并解决问题。

问题描述

我正在尝试优化一种计算密集型算法,并遇到某些缓存问题.我有一个巨大的缓冲区,偶尔和随机编写,并且在应用程序的末尾只读一次.显然,写入缓冲区会产生许多缓存错过,除了污染后来再次需要进行计算所需的缓存.我试图使用非颞型移动仪器,但仍会发生缓存错过(由Valgrind报告,并由运行时测量支持).但是,为了进一步调查非时空的动作,我编写了一个小测试程序,您可以在下面看到.顺序访问,大型缓冲区,仅写作.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <smmintrin.h>

void tim(const char *name, void (*func)()) {
    struct timespec t1, t2;
    clock_gettime(CLOCK_REALTIME, &t1);
    func();
    clock_gettime(CLOCK_REALTIME, &t2);
    printf("%s : %f s.\n", name, (t2.tv_sec - t1.tv_sec) + (float) (t2.tv_nsec - t1.tv_nsec) / 1000000000);
}

const int CACHE_LINE = 64;
const int FACTOR = 1024;
float *arr;
int length;

void func1() {
    for(int i = 0; i < length; i++) {
        arr[i] = 5.0f;
    }
}

void func2() {
    for(int i = 0; i < length; i += 4) {
        arr[i] = 5.0f;
        arr[i+1] = 5.0f;
        arr[i+2] = 5.0f;
        arr[i+3] = 5.0f;
    }
}

void func3() {
    __m128 buf = _mm_setr_ps(5.0f, 5.0f, 5.0f, 5.0f);
    for(int i = 0; i < length; i += 4) {
        _mm_stream_ps(&arr[i], buf);
    }
}

void func4() {
    __m128 buf = _mm_setr_ps(5.0f, 5.0f, 5.0f, 5.0f);
    for(int i = 0; i < length; i += 16) {
        _mm_stream_ps(&arr[i], buf);
        _mm_stream_ps(&arr[4], buf);
        _mm_stream_ps(&arr[8], buf);
        _mm_stream_ps(&arr[12], buf);
    }
}

int main() {
    length = CACHE_LINE * FACTOR * FACTOR;

    arr = malloc(length * sizeof(float));
    tim("func1", func1);
    free(arr);

    arr = malloc(length * sizeof(float));
    tim("func2", func2);
    free(arr);

    arr = malloc(length * sizeof(float));
    tim("func3", func3);
    free(arr);

    arr = malloc(length * sizeof(float));
    tim("func4", func4);
    free(arr);

    return 0;
}

函数1是幼稚的方法,功能2使用循环展开.函数3使用MOVNTP,实际上至少在我检查-O0时,至少在组件中插入了MOVNTP.在功能4中,我试图立即发布多个MOVNTP指令,以帮助CPU完成其写入组合.我用gcc -g -lrt -std=gnu99 -OX -msse4.1 test.c编译了代码,其中X是[0..3]之一.结果很有趣,充其量是:

-O0
func1 : 0.407794 s.
func2 : 0.320891 s.
func3 : 0.161100 s.
func4 : 0.401755 s.
-O1
func1 : 0.194339 s.
func2 : 0.182536 s.
func3 : 0.101712 s.
func4 : 0.383367 s.
-O2
func1 : 0.108488 s.
func2 : 0.088826 s.
func3 : 0.101377 s.
func4 : 0.384106 s.
-O3
func1 : 0.078406 s.
func2 : 0.084927 s.
func3 : 0.102301 s.
func4 : 0.383366 s.

您可以看到_mm_stream_ps比其他GCC优化程序时要快一点,但是当GCC优化打开时,其目的会显着失败. valgrind仍然报告很多缓存写作.

因此,问题是:即使我使用NTA流媒体说明,为什么(L1+LL)缓存失误仍会发生?为什么尤其是func4这么慢?有人可以解释/推测这里发生了什么?

推荐答案

  1. 可能,您的基准测量主要是内存分配性能,而不仅仅是写入性能.您的操作系统可能不是在malloc中分配内存页面,而是在第一次触摸中分配func*函数内部的内存页面. OS在分配大量内存后也可能会进行一些内存混合,因此在内存分配后执行的任何基准测试都可能不可靠.
  2. 您的代码具有 Aliasing 问题:编译器无法保证您的阵列指针在此过程中不会发生变化填充此数组,因此它必须始终从内存中加载arr值,而不是使用寄存器.这可能会使性能下降.避免混叠的最简单方法是将arr和length复制到本地变量,并仅使用局部变量填充数组.有许多众所周知的建议可以避免全球变量.混叠是原因之一.
  3. _mm_stream_ps如果数组由64个字节对齐,则效果更好.在您的代码中,不保证对齐(实际上,malloc将其对齐16个字节).此优化仅在短数组中引人注目.
  4. 完成_mm_stream_ps后,致电_mm_mfence是个好主意.这是正确性所需的,而不是表现.

其他推荐答案

不应该func4是这样:

void func4() {
    __m128 buf = _mm_setr_ps(5.0f, 5.0f, 5.0f, 5.0f);
    for(int i = 0; i < length; i += 16) {
        _mm_stream_ps(&arr[i], buf);
        _mm_stream_ps(&arr[i+4], buf);
        _mm_stream_ps(&arr[i+8], buf);
        _mm_stream_ps(&arr[i+12], buf);
    }
}

本文地址:https://www.itbaoku.cn/post/359056.html