Почему этот код C быстрее, чем этот код C ++? получить самую большую строку в файле - PullRequest
29 голосов
/ 13 января 2012

У меня есть две версии программы, которая делает одно и то же, получая наибольшую длину строки в файле, у меня есть файл с примерно 8 тысячами строк, мой код на C немного более примитивен (из конечно!), чем код, который я имею в C ++. Программе на C требуется около 2 секунд, а программе на C ++ - 10 секунд (один и тот же файл, который я тестирую для обоих случаев). Но почему? Я ожидал, что это займет столько же времени или немного больше, но не на 8 секунд медленнее!

мой код в C:

#include <stdio.h>
#include <stdlib.h> 
#include <string.h>

#if _DEBUG
    #define DEBUG_PATH "../Debug/"
#else
    #define DEBUG_PATH ""
#endif

const char FILE_NAME[] = DEBUG_PATH "data.noun";

int main()
{   
    int sPos = 0;
    int maxCount = 0;
    int cPos = 0;
    int ch;
    FILE *in_file;              

    in_file = fopen(FILE_NAME, "r");
    if (in_file == NULL) 
    {
        printf("Cannot open %s\n", FILE_NAME);
        exit(8);
    }       

    while (1) 
    {
        ch = fgetc(in_file);
        if(ch == 0x0A || ch == EOF) // \n or \r or \r\n or end of file
        {           
            if ((cPos - sPos) > maxCount)
                maxCount = (cPos - sPos);

            if(ch == EOF)
                break;

            sPos = cPos;
        }
        else
            cPos++;
    }

    fclose(in_file);

    printf("Max line length: %i\n",  maxCount); 

    getch();
    return (0);
}

мой код на C ++:

#include <iostream>
#include <fstream>
#include <stdio.h>
#include <string>

using namespace std;

#ifdef _DEBUG
    #define FILE_PATH "../Debug/data.noun"
#else
    #define FILE_PATH "data.noun"
#endif

int main()
{
    string fileName = FILE_PATH;
    string s = "";
    ifstream file;
    int size = 0;

    file.open(fileName.c_str());
    if(!file)
    {
        printf("could not open file!");
        return 0;
    }

    while(getline(file, s) )
            size = (s.length() > size) ? s.length() : size;
    file.close();

    printf("biggest line in file: %i", size);   

    getchar();
    return 0;
}

Ответы [ 8 ]

75 голосов
/ 13 января 2012

Я предполагаю, что это проблема с опциями компилятора, которые вы используете, самим компилятором или файловой системой. Я только что скомпилировал обе версии (с включенной оптимизацией) и запустил их для текстового файла с 92 000 строк:

c++ version:  113 ms
c version:    179 ms

И я подозреваю, что причина того, что версия C ++ быстрее, в том, что fgetc, скорее всего, медленнее. fgetc использует буферизованный ввод / вывод, но выполняет вызов функции для извлечения каждого символа. Я проверял это раньше, и fgetc не так быстро, как вызов, чтобы прочитать всю строку за один вызов (например, по сравнению с fgets).

29 голосов
/ 13 января 2012

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

Сначала я реализовал версии fgetc и getline и рассчитал их время. Я подтвердил, что в режиме отладки версия getline работает медленнее, около 130 мкс против 60 мкс для версии fgetc. Это неудивительно, учитывая общепринятое мнение, что iostreams медленнее, чем использование stdio. Однако в прошлом я знал, что iostreams значительно ускоряются благодаря оптимизации. Это подтвердилось, когда я сравнил время выхода в режим релиза: около 20 мкс с использованием getline и 48 мкс с использованием fgetc.

Тот факт, что использование getline с iostreams быстрее, чем fgetc, по крайней мере в режиме выпуска, противоречит тому, что копирование всех этих данных должно выполняться медленнее, чем не копирование, поэтому я не уверен, на что способна вся оптимизация чтобы избежать, и я действительно не пытался найти какое-либо объяснение, но было бы интересно понять, что оптимизируется. редактировать: когда я смотрел на программы с профилировщиком, было неочевидно, как сравнивать производительность, так как разные методы выглядели настолько отличающимися друг от друга

Кстати, я хотел посмотреть, смогу ли я получить более быструю версию, избегая копирования с использованием метода get() для объекта fstream и просто точно делая то, что делает C-версия. Когда я сделал это, я был очень удивлен, обнаружив, что использование fstream::get() было немного медленнее, чем методы fgetc и getline в отладке и выпуске; Около 230 мкс в отладке и 80 мкс в версии.

Чтобы сузить все замедления, я пошел дальше и сделал другую версию, на этот раз используя stream_buf, прикрепленный к объекту fstream, и метод snextc() для этого. Эта версия, безусловно, самая быстрая; 25 мкс в отладке и 6 мкс в выпуске.

Я предполагаю, что метод, который делает fstream::get() намного медленнее, заключается в том, что он создает объекты часового для каждого вызова. Хотя я не проверял это, я не вижу, что get() делает гораздо больше, чем просто получает следующий символ из stream_buf, за исключением этих сторожевых объектов.

В любом случае, мораль этой истории в том, что если вы хотите быстро io, вам, вероятно, лучше использовать функции iostream высокого уровня, а не stdio, и для действительно быстрого доступа к базовому stream_buf. edit: на самом деле эта мораль может относиться только к MSVC, см. Обновление внизу для результатов из другого набора инструментов.

Для справки:

Я использовал VS2010 и Chrono от Boost 1.47 для синхронизации. Я построил 32-битные двоичные файлы (кажется, требуется для Chrono Boost, потому что он не может найти 64-битную версию этой библиотеки). Я не настраивал параметры компиляции, но они могут быть не совсем стандартными, так как я делал это в проекте «нуля против проекта», который я храню.

Файл, который я тестировал, представлял собой открытую текстовую версию Oeuvres Complètes de Frédéric Bastiat объемом 11 000 МБ, том 1 Фредерика Бастиата из Project Gutenberg, http://www.gutenberg.org/ebooks/35390

Время выхода из режима

fgetc time is: 48150 microseconds
snextc time is: 6019 microseconds
get time is: 79600 microseconds
getline time is: 19881 microseconds

Время режима отладки:

fgetc time is: 59593 microseconds
snextc time is: 24915 microseconds
get time is: 228643 microseconds
getline time is: 130807 microseconds

Вот моя fgetc() версия:

{
    auto begin = boost::chrono::high_resolution_clock::now();
    FILE *cin = fopen("D:/bames/automata/pg35390.txt","rb");
    assert(cin);
    unsigned maxLength = 0;
    unsigned i = 0;
    int ch;
    while(1) {
        ch = fgetc(cin);
        if(ch == 0x0A || ch == EOF) {
            maxLength = std::max(i,maxLength);
            i = 0;
            if(ch==EOF)
                break;
        } else {
            ++i;
        }
    }
    fclose(cin);
    auto end = boost::chrono::high_resolution_clock::now();
    std::cout << "max line is: " << maxLength << '\n';
    std::cout << "fgetc time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}

Вот моя getline() версия:

{
    auto begin = boost::chrono::high_resolution_clock::now();
    std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
    unsigned maxLength = 0;
    std::string line;
    while(std::getline(fin,line)) {
        maxLength = std::max(line.size(),maxLength);
    }
    auto end = boost::chrono::high_resolution_clock::now();
    std::cout << "max line is: " << maxLength << '\n';
    std::cout << "getline time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}

fstream::get() версия

{
    auto begin = boost::chrono::high_resolution_clock::now();
    std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
    unsigned maxLength = 0;
    unsigned i = 0;
    while(1) {
        int ch = fin.get();
        if(fin.good() && ch == 0x0A || fin.eof()) {
            maxLength = std::max(i,maxLength);
            i = 0;
            if(fin.eof())
                break;
        } else {
            ++i;
        }
    }
    auto end = boost::chrono::high_resolution_clock::now();
    std::cout << "max line is: " << maxLength << '\n';
    std::cout << "get time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}

и snextc() версия

{
    auto begin = boost::chrono::high_resolution_clock::now();
    std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
    std::filebuf &buf = *fin.rdbuf();
    unsigned maxLength = 0;
    unsigned i = 0;
    while(1) {
        int ch = buf.snextc();
        if(ch == 0x0A || ch == std::char_traits<char>::eof()) {
            maxLength = std::max(i,maxLength);
            i = 0;
            if(ch == std::char_traits<char>::eof())
                break;
        } else {
            ++i;
        }
    }
    auto end = boost::chrono::high_resolution_clock::now();
    std::cout << "max line is: " << maxLength << '\n';
    std::cout << "snextc time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}

Обновление:

Я перезапустил тесты, используя clang (trunk) на OS X с libc ++. Результаты для реализаций на основе iostream остались относительно одинаковыми (с включенной оптимизацией); fstream::get() намного медленнее, чем std::getline() намного медленнее, чем filebuf::snextc(). Но производительность fgetc() улучшилась по сравнению с реализацией getline() и стала быстрее. Возможно, это связано с тем, что копирование, выполняемое getline(), становится проблемой для этого набора инструментов, тогда как для MSVC этого не было? Может быть, реализация fgetc () в CRT от Microsoft плохая или что-то в этом роде?

В любом случае, вот время (я использовал гораздо больший файл, 5,3 МБ):

с использованием -Os

fgetc time is: 39004 microseconds
snextc time is: 19374 microseconds
get time is: 145233 microseconds
getline time is: 67316 microseconds

с использованием -O0

fgetc time is: 44061 microseconds
snextc time is: 92894 microseconds
get time is: 184967 microseconds
getline time is: 209529 microseconds

-O2

fgetc time is: 39356 microseconds
snextc time is: 21324 microseconds
get time is: 149048 microseconds
getline time is: 63983 microseconds

-O3

fgetc time is: 37527 microseconds
snextc time is: 22863 microseconds
get time is: 145176 microseconds
getline time is: 67899 microseconds
14 голосов
/ 13 января 2012

Версия C ++ постоянно размещает и освобождает экземпляры std :: string. Выделение памяти является дорогостоящей операцией. В дополнение к этому выполняются конструкторы / деструкторы.

Однако в версии C используется постоянная память, и это было просто необходимо: чтение по одному символу, установка счетчика длины строки в новое значение, если оно выше, для каждой новой строки и все.

11 голосов
/ 13 января 2012

Вы не сравниваете яблоки с яблоками. Ваша программа на C не копирует данные из буфера FILE* в память вашей программы. Он также работает с необработанными файлами.

Ваша программа на C ++ должна пересекать длину каждой строки несколько раз - один раз в коде потока, чтобы знать, когда завершать возвращаемую строку, один раз в конструкторе std::string, и один раз в Ваш код звонит на s.length().

Вполне возможно, что вы могли бы улучшить производительность вашей C-программы, например, используя getc_unlocked, если она доступна для вас. Но самая большая победа заключается в том, что вам не нужно копировать ваши данные.

РЕДАКТИРОВАТЬ: отредактировано в ответ на комментарий bames53

5 голосов
/ 13 января 2012

Я попытался скомпилировать и запустить ваши программы на 40 тыс. Строк исходного кода C ++, и они оба завершились примерно за 25 мс или около того. Я могу только заключить, что ваши входные файлы имеют чрезвычайно длинные строки, возможно, 10K-100K символов в строке. В этом случае версия C не имеет отрицательной производительности по сравнению с длинной строкой, в то время как версия C ++ должна будет постоянно увеличивать размер строки и копировать старые данные в новый буфер. Если бы он увеличился в размере достаточное количество раз, это могло бы объяснить чрезмерную разницу в производительности.

Ключевым моментом здесь является то, что две программы не делают одно и то же, поэтому вы не можете реально сравнить их результаты. Если бы вы смогли предоставить входной файл, мы могли бы предоставить дополнительную информацию.

Возможно, вы могли бы использовать tellg и ignore, чтобы сделать это быстрее в C ++.

5 голосов
/ 13 января 2012

2 секунды всего за 8.000 строк?Я не знаю, какова длина ваших строк, но есть вероятность, что вы делаете что-то очень неправильное.

Эта тривиальная программа на Python выполняется почти мгновенно, когда El Quijote загружен из Project Gutenberg (40006 строк, 2,2 МБ):

import sys
print max(len(s) for s in sys.stdin)

Время:

~/test$ time python maxlen.py < pg996.txt
76

real    0m0.034s
user    0m0.020s
sys     0m0.010s

Вы можете улучшить свой код C, буферизуя ввод, а не читая char по char.

О том, почему C ++медленнее, чем C, это должно быть связано со сборкой строковых объектов и последующим вызовом метода length.В Си вы просто подсчитываете символы на ходу.

3 голосов
/ 13 января 2012

Программа C ++ создает строковые объекты из строк, в то время как программа C просто читает символы и просматривает символы.

EDIT:

Спасибо за отклики, но после обсуждения я думаю, что этот ответ неправильный. Это было разумное первое предположение, но в этом случае кажется, что разное (и очень медленное) время выполнения вызвано другими причинами.

0 голосов
/ 27 июня 2012

Я в порядке с людьми теории.Но давайте станем эмпирическими.

Я создал файл с 13 миллионами строк текстового файла для работы.

~$ for i in {0..1000}; do cat /etc/* | strings; done &> huge.txt

Исходный код, отредактированный для чтения из stdin (не должен сильно влиять на производительность)сделал это почти за 2 мин.

C ++ код:

#include <iostream>
#include <stdio.h>

using namespace std;

int main(void)
{
    string s = "";
    int size = 0;

    while (cin) {
        getline(cin, s);
        size = (s.length() > size) ? s.length() : size;
    }
    printf("Biggest line in file: %i\n", size);

    return 0;
}

C ++ время:

~$ time ./cplusplus < huge.txt
real    1m53.122s
user    1m29.254s
sys     0m0.544s

A 'C' версия:

#include <stdio.h>
int main(void)
{
    char *line = NULL;
    int read, max = 0, len = 0;

    while ((read = getline(&line, &len, stdin)) != -1)
        if (max < read)
            max = read -1;
    printf("Biggest line in file %d\n", max);

    return 0;
}
*Производительность 1017 * C:
~$ time ./ansic < huge.txt
real    0m4.015s
user    0m3.432s
sys     0m0.328s

Делайте математику ...

...