Почему std :: fill (0) медленнее, чем std :: fill (1)?

Я наблюдал в системе, котораяstd::fill на большомstd::vector<int> был значительно и постоянно медленнее при установке постоянного значения0 по сравнению с постоянным значением1 или динамическое значение:

5,8 ГБ / с против 7,5 ГБ / с

Однако результаты отличаются для меньших размеров данных, гдеfill(0) быстрее

С более чем одним потоком, с размером данных 4 ГиБ,fill(1) показывает более высокий уклон, но достигает гораздо более низкого пика, чемfill(0) (51 ГиБ / с против 90 ГиБ / с):

Это поднимает вторичный вопрос, почему максимальная пропускная способностьfill(1) намного ниже.

Тестовой системой для этого был двухпроцессорный процессор Intel Xeon E5-2680 v3 с тактовой частотой 2,5 ГГц (через/sys/cpufreq) с 8x16 ГиБ DDR4-2133. Я тестировал с GCC 6.1.0 -O3) и компилятор Intel 17.0.1 -fast), оба получают идентичные результаты.GOMP_CPU_AFFINITY=0,12,1,13,2,14,3,15,4,16,5,17,6,18,7,19,8,20,9,21,10,22,11,23 был установлен. Strem / add / 24 темы получают 85 ГБ / с в системе.

Я смог воспроизвести этот эффект на другой системе с двумя сокетами Haswell, но не на любой другой архитектуре. Например, в Sandy Bridge EP производительность памяти идентична, тогда как в кешеfill(0) намного быстрее.

Вот код для воспроизведения:

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <omp.h>
#include <vector>

using value = int;
using vector = std::vector<value>;

constexpr size_t write_size = 8ll * 1024 * 1024 * 1024;
constexpr size_t max_data_size = 4ll * 1024 * 1024 * 1024;

void __attribute__((noinline)) fill0(vector& v) {
    std::fill(v.begin(), v.end(), 0);
}

void __attribute__((noinline)) fill1(vector& v) {
    std::fill(v.begin(), v.end(), 1);
}

void bench(size_t data_size, int nthreads) {
#pragma omp parallel num_threads(nthreads)
    {
        vector v(data_size / (sizeof(value) * nthreads));
        auto repeat = write_size / data_size;
#pragma omp barrier
        auto t0 = omp_get_wtime();
        for (auto r = 0; r < repeat; r++)
            fill0(v);
#pragma omp barrier
        auto t1 = omp_get_wtime();
        for (auto r = 0; r < repeat; r++)
            fill1(v);
#pragma omp barrier
        auto t2 = omp_get_wtime();
#pragma omp master
        std::cout << data_size << ", " << nthreads << ", " << write_size / (t1 - t0) << ", "
                  << write_size / (t2 - t1) << "\n";
    }
}

int main(int argc, const char* argv[]) {
    std::cout << "size,nthreads,fill0,fill1\n";
    for (size_t bytes = 1024; bytes <= max_data_size; bytes *= 2) {
        bench(bytes, 1);
    }
    for (size_t bytes = 1024; bytes <= max_data_size; bytes *= 2) {
        bench(bytes, omp_get_max_threads());
    }
    for (int nthreads = 1; nthreads <= omp_get_max_threads(); nthreads++) {
        bench(max_data_size, nthreads);
    }
}

Представленные результаты скомпилированы сg++ fillbench.cpp -O3 -o fillbench_gcc -fopenmp.

Ответы на вопрос(2)

Ваш ответ на вопрос