Эквивалент С++ для массива 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

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