Почему _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 использует movntps, который фактически был вставлен в сборку, по крайней мере, когда я проверял -O0. В функции 4 я попытался выполнить сразу несколько инструкций movntps, чтобы помочь процессору выполнить комбинирование записи. Я скомпилировал код с помощью 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 по-прежнему сообщает о множестве промахов при записи в кеш.

Итак, вопросы: почему эти (L1 + LL) промахи кеша все еще происходят, даже если я использую инструкции потоковой передачи NTA? Почему особенно func4 такой медленный ?! Может ли кто-нибудь объяснить / предположить, что здесь происходит?

7
задан Billy ONeal 30 January 2012 в 17:58
поделиться