пятница, 10 декабря 2010 г.

std::vector vs plain array

Йоханга.

Сегодня хочу вам немного рассказать о C++. Это мой любимый язык программирования, несмотря на все его достоинства и недостатки (-: .

Часто на просторах интернета, да и не только, я слышу, что раньше трава была зеленее, собаки толще, а С быстрее С++. К примеру, мол, работа с массивами. Доступ по индексу вот в таком коде:

int *arr = new[100];

arr[99] = 1;

Быстрее, чем вот в таком:

std::vector arr(100);

arr[99] = 1;

Предлагаю простой пример. Есть алгоритм, изначально написанный на "чистом С".

По словам автора функции:

Код IDCT в JPEG декодере, мною реализован, работает с матрицами
превращает все элементы matrix из частот в значения цветовых компонент

Вот код:

#define JPEG_COS1 363604
#define JPEG_COS2 342507
#define JPEG_COS3 308248
#define JPEG_SIN1 72325
#define JPEG_SIN2 141871
#define JPEG_SIN3 205965
#define JPEG_SQRT2 185364

static void __fastcall JpegIDCTс(long *matrix)
{
long tmp[64], *ptr1, *ptr2;
long a0, a1, a2, a3, a4, a5, a6, a7;
long b0, b1, b2, b3, b4, b5, b6, b7;
long c0, c1, c2, c3;
long r;
int t;

ptr1 = matrix;
ptr2 = tmp;
t = 8;
while(t--)
{
a0 = *ptr1;
ptr1++;
a4 = *ptr1;
ptr1++;
a2 = *ptr1;
ptr1++;
a6 = *ptr1;
ptr1++;
a1 = *ptr1;
ptr1++;
a5 = *ptr1;
ptr1++;
a3 = *ptr1;
ptr1++;
a7 = *ptr1;
ptr1++;

c0 = (a0 + a1);
c1 = (a0 - a1);
r = JPEG_SIN2 * (a2 + a3);
c2 = (r - (JPEG_SIN2+JPEG_COS2) * a3) >> 18;
c3 = (r - (JPEG_SIN2-JPEG_COS2) * a2) >> 18;
r = JPEG_SIN1 * (a4 + a7);
b4 = (r - (JPEG_SIN1+JPEG_COS1) * a7) >> 18;
b7 = (r - (JPEG_SIN1-JPEG_COS1) * a4) >> 18;
r = JPEG_COS3 * (a5 + a6);
b5 = (r - (JPEG_COS3+JPEG_SIN3) * a6) >> 18;
b6 = (r - (JPEG_COS3-JPEG_SIN3) * a5) >> 18;

a4 = b4 + b5;
a5 = b5 - b4;
a6 = b7 - b6;
a7 = b7 + b6;

b5 = a6 + a5;
b6 = a6 - a5;

a5 = (JPEG_SQRT2*b5) >> 18;
a6 = (JPEG_SQRT2*b6) >> 18;

b0 = c0 + c3;
b1 = c1 + c2;
b2 = c1 - c2;
b3 = c0 - c3;

*ptr2 = (b0 + a7);
ptr2++;
*ptr2 = (b1 + a6);
ptr2++;
*ptr2 = (b2 + a5);
ptr2++;
*ptr2 = (b3 + a4);
ptr2++;
*ptr2 = (b3 - a4);
ptr2++;
*ptr2 = (b2 - a5);
ptr2++;
*ptr2 = (b1 - a6);
ptr2++;
*ptr2 = (b0 - a7);
ptr2++;
}
ptr1 = matrix;
ptr2 = tmp;
t = 8;
while(t--)
{
a0 = ptr2[(0 << a1 =" ptr2[(4" a2 =" ptr2[(2" a3 =" ptr2[(6" a4 =" ptr2[(1" a5 =" ptr2[(5" a6 =" ptr2[(3" a7 =" ptr2[(7" c0 =" (a0" c1 =" (a0" r =" JPEG_SIN2" c2 =" (r">> 18;
c3 = (r - (JPEG_SIN2-JPEG_COS2) * a2) >> 18;
r = JPEG_SIN1 * (a4 + a7);
b4 = (r - (JPEG_SIN1+JPEG_COS1) * a7) >> 18;
b7 = (r - (JPEG_SIN1-JPEG_COS1) * a4) >> 18;
r = JPEG_COS3 * (a5 + a6);
b5 = (r - (JPEG_COS3+JPEG_SIN3) * a6) >> 18;
b6 = (r - (JPEG_COS3-JPEG_SIN3) * a5) >> 18;

a4 = b4 + b5;
a5 = b5 - b4;
a6 = b7 - b6;
a7 = b7 + b6;

b5 = a6 + a5;
b6 = a6 - a5;

a5 = (JPEG_SQRT2*b5) >> 18;
a6 = (JPEG_SQRT2*b6) >> 18;

b0 = c0 + c3;
b1 = c1 + c2;
b2 = c1 - c2;
  b3 = c0 - c3;

ptr1[(0 <<>> 3;

ptr1[(1 <<>> 3;

ptr1[(2 <<>> 3;

ptr1[(3 <<>> 3;

ptr1[(4 <<>> 3;

ptr1[(5 <<>> 3;

ptr1[(6 <<>> 3;

ptr1[(7 <<>> 3;
ptr1++;
ptr2++;
}
}

Достаточно плотная работа с массивом.

Что мы тут имеем? Имеем работу с динамическим массивом, эквивалент которого в С++ это std::vector и статический массив, эквивалента которого в стандартной библиотеке С++ пока что нет, но зато есть эквивалент в бусте: boost::array.

Итак, меняем следующие вещи:

0) Копируем функцию куда-нибудь и называем ее JpegIDCTcpp.

1) Входящий параметр делаем std::vector &

2) Переменную long tmp[64]; меняем на boost::array tmp;

3) Указтель long *ptr1; меняем на std::vector::iterator ptr1;

4) Указатель long *ptr2; меняем на boost::array::iterator ptr2;


Пишем следующий тест:

int main()

{   // First function   Collect v(64);

   unsigned long t1 = millis();

   for ( int i = 0; i <>

  {

    JpegIDCTcpp(v);

  }

  unsigned long t2 = millis();

  std::cout << "vector time = "<< (t2 - t1) <<>

  // Second function

  long * a = new long[64];

  t1 = millis();

  for ( int i = 0; i <>

  {

    JpegIDCTc(a);

  }

  t2 = millis();

  std::cout << "array time = "<< (t2 - t1) <<>

  delete []a;

  return 0;

}

Код был оттестирован двумя компиялторами: GCC и входящим в Microsoft Visual Studio 2008. Сразу оговорюсь, что он тестировался на разных компьютерах, поэтому разбежка между ними получилась ощутимая.

Компилируем в gcc c ключем -O2, запускаем:

vector time = 11595
array time = 11485

Компилируем в gcc c ключем -O3, запускаем:

vector time = 8977
array time = 8998

Компилируем nmake, получаем:

vector time = 11237
array time = 3136

Почему такая разница? Потому что в реализации STL от Майкрософта вектор проверяет выход за пределы массива даже в релизе. Но этого можно избежать, достаточно добавить вот такой дефайн:

#define _SECURE_SCL 0

Итак, результаты трех тестов после отключения проверки индексов в релизе следующие:

1.
vector: 3167
array: 3167

2.
vector: 3136
array: 3182

3.
vector: 3183
array: 3229

Разница во всех примерах находится в пределах погрешности, так что скорость можно считать эквивалентной.

Откуда же такая магия? Всё дело в том, что на этапе компиляции видна вся реализация шаблона в конкретной точке использования, что дает развернуться оптимизатору. В итоге многие функции оказываются встроенными, что дает практически такой же код, как если бы мы использовали "массивы в стиле С".

В чем мораль? Мораль в том, что при одинаковой скорости выполнения код на С++, с использованием шаблонных классов std::vector и boost::array, а тах же их итераторов, намного безопаснее кода "На чистом С": В дебаге есть проверки выхода за пределы массива, есть контроль за памятью и освобождением ресурсов, удобство работы с вектором ни в какое сравнение не идет с "голым массивом".

Надеюсь, вы сделаете правильный выбор.

Здесь можно скачать весь пример.

Так же можно посмотреть ветвь дискуссии здесь.