Отличный вопрос. Я пришел сюда, ожидая найти какое-то простое исправление, которое ускорит векторные тесты. Это не сработало так, как я ожидал!
Оптимизация помогает, но этого недостаточно. С оптимизацией я все еще вижу разницу в производительности в 2 раза между UseArray и UseVector. Интересно, что UseVector был значительно медленнее, чем UseVectorPushBack без оптимизации.
# g++ -Wall -Wextra -pedantic -o vector vector.cpp
# ./vector
UseArray completed in 20.68 seconds
UseVector completed in 120.509 seconds
UseVectorPushBack completed in 37.654 seconds
The whole thing completed in 178.845 seconds
# g++ -Wall -Wextra -pedantic -O3 -o vector vector.cpp
# ./vector
UseArray completed in 3.09 seconds
UseVector completed in 6.09 seconds
UseVectorPushBack completed in 9.847 seconds
The whole thing completed in 19.028 seconds
Идея № 1 - использовать новый [] вместо malloc
Я попытался изменить malloc()
на new[]
в UseArray, чтобы объекты были построены. И переход от индивидуального назначения поля к назначению экземпляра Pixel. Да, и переименование переменной внутреннего цикла в j
.
void UseArray()
{
TestTimer t("UseArray");
for(int i = 0; i < 1000; ++i)
{
int dimension = 999;
// Same speed as malloc().
Pixel * pixels = new Pixel[dimension * dimension];
for(int j = 0 ; j < dimension * dimension; ++j)
pixels[j] = Pixel(255, 0, 0);
delete[] pixels;
}
}
Удивительно (для меня), ни одно из этих изменений не имело никакого значения вообще. Даже не изменение на new[]
, которое по умолчанию создаст все пиксели. Кажется, что gcc может оптимизировать вызовы конструктора по умолчанию при использовании new[]
, но не при использовании vector
.
Идея № 2 - Удаление повторных вызовов оператора []
Я также попытался избавиться от тройного поиска operator[]
и кэшировать ссылку на pixels[j]
. Это на самом деле замедлило UseVector! К сожалению.
for(int j = 0; j < dimension * dimension; ++j)
{
// Slower than accessing pixels[j] three times.
Pixel &pixel = pixels[j];
pixel.r = 255;
pixel.g = 0;
pixel.b = 0;
}
# ./vector
UseArray completed in 3.226 seconds
UseVector completed in 7.54 seconds
UseVectorPushBack completed in 9.859 seconds
The whole thing completed in 20.626 seconds
Идея № 3 - Удалить конструкторов
А как насчет полного удаления конструкторов? Тогда, возможно, gcc сможет оптимизировать построение всех объектов при создании векторов. Что произойдет, если мы изменим Pixel на:
struct Pixel
{
unsigned char r, g, b;
};
Результат: примерно на 10% быстрее. Все еще медленнее, чем массив. Hm.
# ./vector
UseArray completed in 3.239 seconds
UseVector completed in 5.567 seconds
Идея № 4 - Использовать итератор вместо индекса цикла
Как насчет использования vector<Pixel>::iterator
вместо индекса цикла?
for (std::vector<Pixel>::iterator j = pixels.begin(); j != pixels.end(); ++j)
{
j->r = 255;
j->g = 0;
j->b = 0;
}
Результат:
# ./vector
UseArray completed in 3.264 seconds
UseVector completed in 5.443 seconds
Нет, не отличается. По крайней мере, это не медленнее. Я думал, что это будет иметь производительность, аналогичную # 2, где я использовал Pixel&
ссылку.
Заключение
Даже если какой-нибудь умный cookie-файл выяснит, как сделать векторную петлю такой же быстрой, как и в массиве, это не очень хорошо говорит о поведении по умолчанию std::vector
. Так много для того, чтобы компилятор был достаточно умен, чтобы оптимизировать всю C ++ и сделать контейнеры STL такими же быстрыми, как необработанные массивы.
Суть в том, что компилятор не может оптимизировать вызовы конструктора no-op по умолчанию при использовании std::vector
. Если вы используете обычный new[]
, он прекрасно их оптимизирует. Но не с std::vector
. Даже если вы можете переписать свой код, чтобы исключить вызовы конструктора, которые выглядят здесь как мантра: «Компилятор умнее вас. STL такой же быстрый, как простой C. Не беспокойтесь об этом».