Сумма по вектору выполняется по разному для почти идентичного кода - PullRequest
3 голосов
/ 29 января 2020

Кажется, что существует разница в производительности между этими функциями, суммирующими по массиву и вектору при компиляции с g++ flag -O3:

float sum1(float* v, int length) {
    float sum = 0;
    for(int i = 0; i < length; i++) {
        sum += v[i];
    }
    return sum;
}

float sum2(std::vector<float> v) {
    return sum1(&v[0], v.size());
}

При вызове sum1, например, с длиной 100000 и sum2 с вектором одинаковой длины и содержания, sum2 в итоге составит прибл. На 10% медленнее, чем sum1 в моих тестах. Измеренное время выполнения:

sum1: 0.279816 ms
sum2: 0.307811 ms

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

[Обновление] При вызове по ссылке (float sum2(std::vector<float>& v)) разница в производительности составляет прибл. Осталось 3,7%, так что это помогает, но все еще есть какая-то потеря производительности в других местах?

[Update2] В остальном, похоже, статистически преобладают, как видно с большим количеством итераций. Таким образом, единственная проблема - это вызов по ссылке!


Полный тестовый код (скомпилирован с флагом -O3 с g++, также протестирован с clang++):

#include <iostream>
#include <chrono>
#include <vector>

using namespace std;

std::vector<float> fill_vector(int length) {
    std::vector<float> ret;

    for(int i = 0; i < length; i++) {
        float r = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
        ret.push_back(r);
    }

    return ret;
}

float sum1(float* v, int length) {
    float sum = 0;
    for(int i = 0; i < length; i++) {
        sum += v[i];
    }
    return sum;
}

float sum2(std::vector<float> v) {
    return sum1(&v[0], v.size());
}

int main() {
    int iterations = 10000;
    int vector_size = 100000;

    srand(42);
    std::vector<float> v1 = fill_vector(vector_size);

    float* v2;
    v2 = &v1[0];

    std::chrono::duration<double, std::milli> duration_sum1(0);
    for(int i = 0; i < iterations; i++) {
        auto t1 = std::chrono::high_resolution_clock::now();
        float res = sum1(v2, vector_size);
        auto t2 = std::chrono::high_resolution_clock::now();
        cout << "Result sum1: " << res << endl;
        duration_sum1 += t2 - t1;
    }
    duration_sum1 /= iterations;

    std::chrono::duration<double, std::milli> duration_sum2(0);
    for(int i = 0; i < iterations; i++) {
        auto t1 = std::chrono::high_resolution_clock::now();
        float res = sum2(v1);
        auto t2 = std::chrono::high_resolution_clock::now();
        cout << "Result sum2: " << res << endl;
        duration_sum2 += t2 - t1;
    }
    duration_sum2 /= iterations;

    cout << "Durations:" << endl;
    cout << "sum1: " << duration_sum1.count() << " ms" << endl;
    cout << "sum2: " << duration_sum2.count() << " ms" << endl;
}

Ответы [ 3 ]

6 голосов
/ 29 января 2020

Я думаю, что издержки связаны с передачей вектора.

Попробуйте вместо этого передать ссылку:

float sum2(std::vector<float>& v)
2 голосов
/ 29 января 2020

Ваша функция sum2() принимает std::vector<float> объект по значению :

float sum2(std::vector<float> v) {
    return sum1(&v[0], v.size());
}

В сценарии, подобном этому:

std::vector<float> vec;
// ...
sum2(vec); // copies vec

Объект параметра v приводит к инициализации копии из аргумента vec, переданного sum2(). Это может быть дорогостоящей операцией, особенно если вектор большой. Если вы стремитесь снизить накладные расходы, связанные с вызовом на sum2(), у вас есть следующие варианты:

  • Заставить sum2() принять ссылку на std::vector<float> вместо этого, т.е. std::vector<float>&:

    float sum2(std::vector<float>& v) {
       return sum1(&v[0], v.size());
    }
    

    В этом случае в функцию передается только ссылка на вектор, а не весь вектор, поэтому копия вектора не создается.

  • Вызов sum2() таким образом, что его объект параметра v является инициализированным движением из переданного аргумента (в отличие от инициализированной копии - что вы в настоящее время делаете), если вам больше не нужно содержимое vec после вызова sum2():

    sum2(std::move(vec)); // move instead of copy
    
1 голос
/ 29 января 2020

Чтобы добавить к уже установленному ответу использование ссылки c, чтобы избежать дорогостоящей копии вектора:

При использовании ссылки вы можете go для ссылки на констант.

Вам необходимо изменить

float sum1(float* v, int length)

на

float sum1(const float* v, int length)

и

float sum2(std::vector<float> v)

на

float sum2(const std::vector<float>& v)

Использование эталонных средств что вы не копируете вектор, но это также позволяет sum2 вносить изменения в вектор. Поскольку ваша причина использовать ссылку только для того, чтобы избежать копирования, я думаю, что было бы хорошо заявить, что sum2 не изменяет вектор в его интерфейсе.

Те же логики c для const применяются на sum1 и становится неактуальным, так как вектор констант дает только указатели на констант.

...