Эквивалент С++ для массива C-стиля
Я слышал, что многие ребята говорили, что С++ работает так же быстро или быстрее, чем C во всем, но чище и приятнее.
Хотя я не противоречу тому, что С++ очень изящный и довольно быстрый, я не нашел замены для доступа к важной памяти или приложений, связанных с процессорами.
Вопрос: существует ли эквивалент в С++ для массивов C-стиля с точки зрения производительности?
Приведенный ниже пример надуман, но меня интересует решение для реальных проблем: я разрабатываю приложения для обработки изображений, а объем обработки пикселей там огромен.
double t;
// C++
std::vector<int> v;
v.resize(1000000,1);
int i, j, count = 0, size = v.size();
t = (double)getTickCount();
for(j=0;j<1000;j++)
{
count = 0;
for(i=0;i<size;i++)
count += v[i];
}
t = ((double)getTickCount() - t)/getTickFrequency();
std::cout << "(C++) For loop time [s]: " << t/1.0 << std::endl;
std::cout << count << std::endl;
// C-style
#define ARR_SIZE 1000000
int* arr = (int*)malloc( ARR_SIZE * sizeof(int) );
int ci, cj, ccount = 0, csize = ARR_SIZE;
for(ci=0;ci<csize;ci++)
arr[ci] = 1;
t = (double)getTickCount();
for(cj=0;cj<1000;cj++)
{
ccount = 0;
for(ci=0;ci<csize;ci++)
ccount += arr[ci];
}
free(arr);
t = ((double)getTickCount() - t)/getTickFrequency();
std::cout << "(C) For loop time [s]: " << t/1.0 << std::endl;
std::cout << ccount << std::endl;
Вот результат:
(C++) For loop time [s]: 0.329069
(C) For loop time [s]: 0.229961
Примечание: getTickCount()
происходит от сторонней библиотеки. Если вы хотите протестировать, просто замените свое любимое измерение часов
Update:
Я использую VS 2010, режим Release, все остальное по умолчанию
Ответы
Ответ 1
Вопрос: существует ли эквивалент в С++ для массивов C-стиля с точки зрения производительности?
Ответ: напишите код на С++! Знайте свой язык, знайте свою стандартную библиотеку и используйте ее. Стандартные алгоритмы являются правильными, читабельными и быстрыми (они знают, как лучше реализовать его в текущем компиляторе).
void testC()
{
// unchanged
}
void testCpp()
{
// unchanged initialization
for(j=0;j<1000;j++)
{
// how a C++ programmer accumulates:
count = std::accumulate(begin(v), end(v), 0);
}
// unchanged output
}
int main()
{
testC();
testCpp();
}
Вывод:
(C) For loop time [ms]: 434.373
1000000
(C++) For loop time [ms]: 419.79
1000000
Скомпилирован с g++ -O3 -std=c++0x
Версия 4.6.3 на Ubuntu.
Для вашего кода мой вывод похож на ваш. user1202136 дает хороший ответ о различиях...
Ответ 2
Простой ответ: ваш тест ошибочен.
Более длинный ответ: вам нужно включить полную оптимизацию, чтобы получить преимущество производительности С++. Тем не менее, ваш тест все еще испорчен.
Некоторые наблюдения:
- Если вы включите полную оптимизацию, будет удален очень большой фрагмент for-loop. Это делает ваш тест бессмысленным.
-
std::vector
имеют накладные расходы для динамического перераспределения, попробуйте std::array
.
Чтобы быть конкретным, microsoft stl по умолчанию проверял итератор.
- У вас нет препятствий для предотвращения перекрестного переупорядочения кода кода/кода C/С++.
- (на самом деле не связано)
cout << ccount
знает локаль, printf
не является; std::endl
выполнить сброс вывода, printf("\n")
нет.
"Традиционный" код для отображения преимуществ С++ - это C qsort()
vs С++ std::sort()
. Здесь сияет синтаксис кода.
Если вам нужен пример приложения "real-life". Найдите какой-нибудь материал для умножения raytracer или matrix. Выберите компилятор, который выполняет автоматическую вектологию.
Обновление
Используя онлайн-демонстрацию LLVM, мы видим, что весь цикл переупорядочен. Контрольный код перемещается для запуска, и он переходит к конечной точке цикла в первом цикле для лучшего предсказания ветвления:
(это код С++)
######### jump to the loop end
jg .LBB0_11
.LBB0_3: # %..split_crit_edge
.Ltmp2:
# print the benchmark result
movl $0, 12(%esp)
movl $25, 8(%esp)
movl $.L.str, 4(%esp)
movl std::cout, (%esp)
calll std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
.Ltmp3:
# BB#4: # %_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc.exit
.Ltmp4:
movl std::cout, (%esp)
calll std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
.Ltmp5:
# BB#5: # %_ZNSolsEd.exit
movl %eax, %ecx
movl %ecx, 28(%esp) # 4-byte Spill
movl (%ecx), %eax
movl -24(%eax), %eax
movl 240(%eax,%ecx), %ebp
testl %ebp, %ebp
jne .LBB0_7
# BB#6:
.Ltmp52:
calll std::__throw_bad_cast()
.Ltmp53:
.LBB0_7: # %.noexc41
cmpb $0, 28(%ebp)
je .LBB0_15
# BB#8:
movb 39(%ebp), %al
jmp .LBB0_21
.align 16, 0x90
.LBB0_9: # Parent Loop BB0_11 Depth=1
# => This Inner Loop Header: Depth=2
addl (%edi,%edx,4), %ebx
addl $1, %edx
adcl $0, %esi
cmpl %ecx, %edx
jne .LBB0_9
# BB#10: # in Loop: Header=BB0_11 Depth=1
incl %eax
cmpl $1000, %eax # imm = 0x3E8
######### jump back to the print benchmark code
je .LBB0_3
Мой тестовый код:
std::vector<int> v;
v.resize(1000000,1);
int i, j, count = 0, size = v.size();
for(j=0;j<1000;j++)
{
count = 0;
for(i=0;i<size;i++)
count += v[i];
}
std::cout << "(C++) For loop time [s]: " << t/1.0 << std::endl;
std::cout << count << std::endl;
Ответ 3
Кажется, это проблема с компилятором. Для C-массивов компилятор обнаруживает шаблон, использует автоинтеграцию и испускает инструкции SSE. Для вектора, похоже, не хватает необходимого интеллекта.
Если я заставляю компилятор не использовать SSE, результаты очень похожи (проверено с помощью g++ -mno-mmx -mno-sse -msoft-float -O3
):
(C++) For loop time [us]: 604610
1000000
(C) For loop time [us]: 601493
1000000
Вот код, который сгенерировал этот вывод. Это в основном код в вашем вопросе, но без какой-либо плавающей точки.
#include <iostream>
#include <vector>
#include <sys/time.h>
using namespace std;
long getTickCount()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000000 + tv.tv_usec;
}
int main() {
long t;
// C++
std::vector<int> v;
v.resize(1000000,1);
int i, j, count = 0, size = v.size();
t = getTickCount();
for(j=0;j<1000;j++)
{
count = 0;
for(i=0;i<size;i++)
count += v[i];
}
t = getTickCount() - t;
std::cout << "(C++) For loop time [us]: " << t << std::endl;
std::cout << count << std::endl;
// C-style
#define ARR_SIZE 1000000
int* arr = new int[ARR_SIZE];
int ci, cj, ccount = 0, csize = ARR_SIZE;
for(ci=0;ci<csize;ci++)
arr[ci] = 1;
t = getTickCount();
for(cj=0;cj<1000;cj++)
{
ccount = 0;
for(ci=0;ci<csize;ci++)
ccount += arr[ci];
}
delete arr;
t = getTickCount() - t;
std::cout << "(C) For loop time [us]: " << t << std::endl;
std::cout << ccount << std::endl;
}
Ответ 4
C++ эквивалент массива с динамическим размером будет std::vector
. С++ эквивалент массива фиксированного размера будет std::array
или std::tr1::array
pre-С++ 11.
Если у вашего векторного кода нет повторений, трудно понять, как это может быть значительно медленнее, чем использование динамически выделенного массива C, если вы скомпилируете некоторую оптимизацию.
Примечание: запуск кода, опубликованного, скомпилированного на gcc 4.4.3 на x86, параметры компилятора
g++ -Wall -Wextra -pedantic-errors -O2 -std = С++ 0x
результаты повторяются близко к
(С++) Для времени цикла [us]: 507888
1000000
(C) Для времени цикла [us]: 496659
1000000
Таким образом, казалось бы, на 2% медленнее для варианта std::vector
после небольшого количества испытаний. Я бы рассмотрел эту совместимую производительность.
Ответ 5
То, что вы указываете, это тот факт, что доступ к объектам всегда будет иметь небольшие накладные расходы, поэтому доступ к vector
будет не быстрее, чем доступ к хорошему старым массивам.
Но даже если использование массива является "C-стильным", оно остается С++, поэтому это не будет проблемой.
Затем, как сказал @juanchopanza, в С++ 11 есть std::array
, который может быть более эффективным, чем std::vector
, но специализирован для массива фиксированного размера.
Ответ 6
Обычно компилятор делает всю оптимизацию... Вам нужно выбрать хороший компилятор