Подтвердить что ты не робот

Самый быстрый способ свести на нет std::vector

Предположим, что у меня есть std::vector double, а именно

std::vector<double> MyVec(N);

Где N настолько велика, что производительность имеет значение. Предположим теперь, что MyVec - нетривиальный вектор (т.е. Не является вектором нулей, но был изменен некоторой рутиной). Теперь мне нужна отрицаемая версия вектора: Мне нужно -MyVec.

До сих пор я реализовал его с помощью

std::transform(MyVec.cbegin(),MyVec.cend(),MyVec.begin(),std::negate<double>());

Но, действительно, я не знаю, это ли это что-то разумное или просто супер наивно с моей стороны.

Я делаю это правильно? Или std:: transform - это просто супер медленная процедура в этом случае?

PS: Я использую библиотеки BLAS и LAPACK все время, но я не нашел ничего, что соответствовало бы этой конкретной потребности. Однако, если в BLAS/LAPACK существует такая функция, которая быстрее, чем std:: transform, я был бы рад узнать.

4b9b3361

Ответ 1

#include <vector>
#include <algorithm>
#include <functional> 
void check()
{
    std::vector<double> MyVec(255);
    std::transform(MyVec.cbegin(),MyVec.cend(),MyVec.begin(),std::negate<double>());
}

Этот код на https://godbolt.org/ с опцией копирования - O3 создает приятную сборку

.L3:
[...]
  cmp r8, 254
  je .L4
  movsd xmm0, QWORD PTR [rdi+2032]
  xorpd xmm0, XMMWORD PTR .LC0[rip]
  movsd QWORD PTR [rdi+2032], xmm0
.L4:

Сложно вообразить быстрее. Ваш код уже совершенен, не пытайтесь перехитрить компилятор и использовать чистый код С++, который работает почти каждый раз.

Ответ 2

К счастью, данные в std::vector являются смежными, поэтому вы можете умножить на -1 с использованием векторных свойств (используя нестандартные нагрузки/хранилища и специальную передачу возможного переполнения). Или используйте ippsMulC_64f/ippsMulC_64f_I из библиотеки intel IPP (вы будете пытаться написать что-то быстрее), в которой будут использоваться самые большие векторные регистры, доступные для вашей платформы: https://software.intel.com/en-us/ipp-dev-reference-mulc

Обновление: чтобы устранить некоторую путаницу в комментариях, полная версия Intel IPP бесплатна (хотя вы можете заплатить за поддержку) и поставляется с Linux, Windows и macOS.

Ответ 3

Как уже упоминалось, это полностью зависит от вашего варианта использования. Вероятно, самым простым способом было бы что-то вроде этого:

 struct MyNegatingVect {
     MyVect data;
     bool negated = false;
     void negate() { negated = !negated; }
     // ... setter and getter need indirection ...
     // ..for example
     MyVect::data_type at(size_t index) { return negated ? - data.at(index) : data.at(index);
 };

Может ли это дополнительное косвенное отношение для каждого отдельного доступа превратить отрицание в настройку одиночного bool, как уже упоминалось, в вашем случае использования (на самом деле я сомневаюсь, что существует прецедент, когда это принесет какую-либо измеримую пользу).

Ответ 4

Во-первых, общая функция negate для векторов арифметического типа в качестве примера:

#include <type_traits>
#include <vector>

...

template <typename arithmetic_type> std::vector<arithmetic_type> &
negate (std::vector<arithmetic_type> & v)
{
    static_assert(std::is_arithmetic<arithmetic_type>::value,
        "negate: not an arithmetic type vector");

    for (auto & vi : v) vi = - vi;

    // note: anticipate that a range-based for may be more amenable
    // to loop-unrolling, vectorization, etc., due to fewer compiler
    // template transforms, and contiguous memory / stride.

    // in theory, std::transform may generate the same code, despite
    // being less concise. very large vectors *may* possibly benefit
    // from C++17 'std::execution::par_unseq' policy?

    return v;
}

Ваше желание канонической унарной функции operator - потребует создания временного, в форме:

std::vector<double> operator - (const std::vector<double> & v)
{
    auto ret (v); return negate(ret);
}

Или в общем случае:

template <typename arithmetic_type> std::vector<arithmetic_type>
operator - (const std::vector<arithmetic_type> & v)
{
    auto ret (v); return negate(ret);
}

Не пытайтесь реализовать оператор как:

template <typename arithmetic_type> std::vector<arithmetic_type> &
operator - (std::vector<arithmetic_type> & v)
{
    return negate(v);
}

В то время как (- v) будет отрицать элементы и возвращать измененный вектор без необходимости временного, он прерывает математические соглашения, эффективно устанавливая: v = - v; Если это ваша цель, используйте функцию negate. Не нарушайте ожидаемую оценку оператора!


clang, с включенным avx512, генерирует этот цикл, отрицая впечатляющие 64 удвоения на итерацию - между обработкой до/после длины:

        vpbroadcastq    LCPI0_0(%rip), %zmm0
        .p2align        4, 0x90
LBB0_21:
        vpxorq  -448(%rsi), %zmm0, %zmm1
        vpxorq  -384(%rsi), %zmm0, %zmm2
        vpxorq  -320(%rsi), %zmm0, %zmm3
        vpxorq  -256(%rsi), %zmm0, %zmm4
        vmovdqu64       %zmm1, -448(%rsi)
        vmovdqu64       %zmm2, -384(%rsi)
        vmovdqu64       %zmm3, -320(%rsi)
        vmovdqu64       %zmm4, -256(%rsi)
        vpxorq  -192(%rsi), %zmm0, %zmm1
        vpxorq  -128(%rsi), %zmm0, %zmm2
        vpxorq  -64(%rsi), %zmm0, %zmm3
        vpxorq  (%rsi), %zmm0, %zmm4
        vmovdqu64       %zmm1, -192(%rsi)
        vmovdqu64       %zmm2, -128(%rsi)
        vmovdqu64       %zmm3, -64(%rsi)
        vmovdqu64       %zmm4, (%rsi)
        addq    $512, %rsi              ## imm = 0x200
        addq    $-64, %rdx
        jne     LBB0_21

gcc-7.2.0 генерирует подобный цикл, но, похоже, настаивает на индексированной адресации.

Ответ 5

Использовать for_each

std::for_each(MyVec.begin(), MyVec.end(), [](double& val) { val = -val });

или параллельный С++ 17

std::for_each(std::execution::par_unseq, MyVec.begin(), MyVec.end(), [](double& val) { val = -val });