C ++ - применить std :: exp к std :: vector - PullRequest
1 голос
/ 18 марта 2020

Есть ли более быстрый (с точки зрения производительности) способ, чем просто сделать

std::vector<double> y;
y.reserve(x.size());
for(size_t i = 0; i < x.size(); ++i)
    y.push_back(std::exp(x[i]));

Ответы [ 2 ]

0 голосов
/ 18 марта 2020

push_back, как ни странно, немного перегружены, потому что он на самом деле не знает, что вы зарезервировали достаточно места, поэтому всегда должен проверять. Поскольку эта проверка может изменить поток управления между l oop итерациями, push_back исключает автоматическую c векторизацию компилятором.

Рассмотрим эти две функции, где первая использует push_back, а вторая один изменяет копию (или перемещенное значение) на месте:

auto exp1(std::vector<double> const& xs) -> std::vector<double> {
    auto ys = std::vector<double>{};
    ys.reserve(xs.size());
    for(auto x : xs){ ys.push_back(std::exp(x)); }
}

auto exp2(std::vector<double> xs) -> std::vector<double> {
    for(auto & x : xs){ x = std::exp(x); }
    return xs;
}

Мы рассмотрим вывод сборки , если он скомпилирован в G CC 9.1 с

gcc -std=c++17 -O3 -march=skylake-avx512

Вот внутренний exp1 l oop (встроенный в довольно много дополнительного кода, который никогда не будет выполнен, потому что вы уже reserve d):

.L45:
        add     rbx, 8
        vmovsd  QWORD PTR [r14], xmm0
        add     r14, 8
        cmp     r12, rbx
        je      .L44
.L18:
        vmovsd  xmm0, QWORD PTR [rbx]
        call    exp
        vmovsd  QWORD PTR [rsp], xmm0
        cmp     rbp, r14
        jne     .L45

А вот exp2:

.L53:
        vmovsd  xmm0, QWORD PTR [rbx]
        add     rbx, 8
        call    exp
        vmovsd  QWORD PTR [rbx-8], xmm0
        cmp     rbp, rbx
        jne     .L53

На практике они в основном одинаковы, потому что exp сложный и G CC не знает, как автоматически его векторизовать. Однако рассмотрим случай, когда во внутреннем l oop происходит нечто гораздо более простое:

auto sq1(std::vector<double> const& xs) -> std::vector<double> {
    auto ys = std::vector<double>{};
    ys.reserve(xs.size());
    for(auto x : xs){ ys.push_back(x*x); }
}

auto sq2(std::vector<double> xs) -> std::vector<double> {
    for(auto & x : xs){ x *= x; }
    return xs;
}

Вот внутреннее l oop sq1:

.L89:
        vmovsd  QWORD PTR [rsi], xmm0
        add     rbx, 8
        add     rsi, 8
        mov     QWORD PTR [rsp+24], rsi
        cmp     rbp, rbx
        je      .L72
.L75:
        vmovsd  xmm0, QWORD PTR [rbx]
        mov     rsi, QWORD PTR [rsp+24]
        vmulsd  xmm0, xmm0, xmm0
        vmovsd  QWORD PTR [rsp+8], xmm0
        cmp     rsi, QWORD PTR [rsp+32]
        jne     .L89

Вот sq2 's. Обратите внимание, что он использует регистры vmulpd и ymm и что он скачет на 32 байта за раз, а не на 8 за один раз.

.L11:
        vmovupd ymm0, YMMWORD PTR [rdx]
        add     rdx, 32
        vmulpd  ymm0, ymm0, ymm0
        vmovupd YMMWORD PTR [rdx-32], ymm0
        cmp     rdx, rcx
        jne     .L11

Конечно, этот внутренний фрагмент l oop немного вводит в заблуждение: он скрывает огромное количество кода, используемого для обработки оставшейся части std::vector, если его размер не делится равномерно на 4. Тем не менее, моя главная мысль в том, что да, вы на самом деле можете сделать немного лучше, чем reserve + push_back (это меня немного удивило, когда я впервые узнал), и что было бы значительно лучше, если бы мы не имели дело с exp в частности.

0 голосов
/ 18 марта 2020

Если вам требуется максимальная точность до ближайшего ULP, это, вероятно, так же быстро, как вы собираетесь получить.

Если вы можете принять некоторые ошибки аппроксимации, есть гораздо более быстрые методы, которые используют SIMD .

...