Char * vs String Speed ​​в C ++ - PullRequest
       6

Char * vs String Speed ​​в C ++

4 голосов
/ 21 октября 2010

У меня есть программа на C ++, которая считывает данные из двоичного файла, и изначально я хранил данные в std::vector<char*> data.Я изменил свой код так, что теперь я использую строки вместо char *, чтобы std::vector<std::string> data.Некоторые изменения, которые я должен был сделать, должны были изменить, например, с strcmp на compare.

Однако я видел, что мое время исполнения резко увеличилось.Для примера файла, когда я использовал char *, это заняло 0.38 с, а после преобразования в строку это заняло 1.72 с на моей машине с Linux.Я наблюдал похожую проблему на моей машине с Windows, время выполнения которой увеличилось с 0,59 до 1,05 с.

Я считаю, что эта функция вызывает замедление.Он является частью класса конвертера, обратите внимание на частные переменные, обозначенные _ в конце имени переменной.У меня явно возникают проблемы с памятью, и я застрял между C и C ++ кодом. Я хочу, чтобы это был код C ++, поэтому я обновил код внизу.

Я получаю доступ к ids_ и names_ также много раз в другой функции, поэтому скорость доступа очень высокаважный. Благодаря использованию map вместо двух отдельных векторов, я смог достичь более высоких скоростей с более стабильным кодом C ++.Спасибо всем!

Пример NewList.Txt

2515    ABC 23.5    32  -99 1875.7  1  
1676    XYZ 12.5    31  -97 530.82  2  
279  FOO 45.5    31  -96  530.8  3  

СТАРЫЙ код:

void converter::updateNewList(){
    FILE* NewList;
    char lineBuffer[100];
    char* id = 0;
    char* name = 0;

    int l = 0;
    int n;

    NewList = fopen("NewList.txt","r");
    if (NewList == NULL){
        std::cerr << "Error in reading NewList.txt\n";
        exit(EXIT_FAILURE);
    } 

    while(!feof(NewList)){
        fgets (lineBuffer , 100 , NewList); // Read line    
        l = 0;
        while (!isspace(lineBuffer[l])){
            l = l + 1;
        }

        id = new char[l];
        switch (l){
            case 1: 
                n = sprintf (id, "%c", lineBuffer[0]);
                break;
            case 2:
                n = sprintf (id, "%c%c", lineBuffer[0], lineBuffer[1]);
                break;
            case 3:
                n = sprintf (id, "%c%c%c", lineBuffer[0], lineBuffer[1], lineBuffer[2]);        
                break;
            case 4:
                n = sprintf (id, "%c%c%c%c", lineBuffer[0], lineBuffer[1], lineBuffer[2],lineBuffer[3]);
                break;
            default:
                n = -1;
                break;
        }
        if (n < 0){
            std::cerr << "Error in processing ids from NewList.txt\n";
            exit(EXIT_FAILURE);
        }

        l = l + 1;
        int s = l;
        while (!isspace(lineBuffer[l])){
            l = l + 1;
        }
        name = new char[l-s];
        switch (l-s){
            case 2:
                n = sprintf (name, "%c%c", lineBuffer[s+0], lineBuffer[s+1]);
                break;
            case 3:
                n = sprintf (name, "%c%c%c", lineBuffer[s+0], lineBuffer[s+1], lineBuffer[s+2]);
                break;
            case 4:
                n = sprintf (name, "%c%c%c%c", lineBuffer[s+0], lineBuffer[s+1], lineBuffer[s+2],lineBuffer[s+3]);
                break;
            default:
                n = -1;
                break;
        }
        if (n < 0){
            std::cerr << "Error in processing short name from NewList.txt\n";
            exit(EXIT_FAILURE);
        }


        ids_.push_back ( std::string(id) );
        names_.push_back(std::string(name));
    }

    bool isFound = false;
    for (unsigned int i = 0; i < siteNames_.size(); i ++) {
        isFound = false;
        for (unsigned int j = 0; j < names_.size(); j ++) {
            if (siteNames_[i].compare(names_[j]) == 0){
                isFound = true;
            }
        }
    }

    fclose(NewList);
    delete [] id;
    delete [] name;
}

КОД C ++

void converter::updateNewList(){
    std::ifstream NewList ("NewList.txt");

    while(NewList.good()){
        unsigned int id (0);
        std::string name;

        // get the ID and name
        NewList >> id >> name;

        // ignore the rest of the line
        NewList.ignore( std::numeric_limits<std::streamsize>::max(), '\n');

        info_.insert(std::pair<std::string, unsigned int>(name,id));

    }

    NewList.close();
}

ОБНОВЛЕНИЕ: Следите за вопросом: Узкое место при сравнении строк и спасибо за очень полезную помощь!Я не буду делать эти ошибки в будущем!

Ответы [ 8 ]

7 голосов
/ 21 октября 2010

Полагаю, это должно быть привязано к вектору

О векторе

A std::vector работает с внутренними смежнымимассив, то есть, когда массив заполнен, ему нужно создать еще один, больший массив и скопировать строки одну за другой, что означает создание копии и уничтожение строки с одинаковым содержимым, что является контрпродуктивным...

Чтобы легко это подтвердить, используйте std::vector<std::string *> и посмотрите, есть ли разница в производительности.

Если это так, то вы можете сделать одну из этих четырех вещей:

  1. , если вы знаете (или имеете хорошую идею) об окончательном размере вектора, используйте его метод reserve(), чтобы зарезервировать достаточно места во внутреннем массиве, чтобыизбегайте бесполезных перераспределений.
  2. используйте std::deque, который работает почти как вектор
  3. используйте std::list (который недать вам произвольный доступ к его элементам)
  4. используйте std :: vector

О строке

Примечание. Я предполагаю, что ваши строки \ char * создаются один раз и не изменяются (через realloc, append и т. Д.).).

Если приведенных выше идей недостаточно, то ...

Распределение внутреннего буфера строкового объекта аналогично malloc из char *, поэтому выдолжны видеть небольшие различия или вообще не иметь различий между ними.

Теперь, если ваши char * на самом деле char[SOME_CONSTANT_SIZE], тогда вы избегаете malloc (и, следовательно, будете идти быстрее, чем std :: string).

Редактировать

После прочтения обновленного кода я вижу следующие проблемы:

  1. , если ids_ и names_ являются векторами, и если у вас есть малейшее представление околичество строк, тогда вы должны использовать reserve() для ids_ и и names_
  2. рассмотреть возможность создания ids_ и names_ deque или списков.
  3. faaNames_ должен быть std :: map или дажеstd :: unordered_map (или любой другой hash_map, который есть у вас в компиляторе).Ваш поиск в настоящее время состоит из двух циклов, что довольно дорого и неэффективно.
  4. Подумайте о сравнении длины строк перед сравнением их содержимого.В C ++ длина строки (т.е. std :: string :: length ()) является операцией с нулевой стоимостью)
  5. Теперь я не знаю, что вы делаете с переменной isFound, ноесли вам нужно найти только ОДНО истинное равенство, то, я думаю, вам следует поработать над алгоритмом (я не знаю, существует ли он уже, см. http://www.cplusplus.com/reference/algorithm/),, но я считаю, что этот поиск можно сделать намного более эффективнымпросто подумав об этом.

Другие комментарии:

  1. Забудьте об использовании int для размеров и длин в STL. По крайней мере, используйте size_t.В 64-битном формате size_t станет 64-битным, в то время как int останется 32-битным, поэтому ваш код не готов к 64-битному режиму (с другой стороны, я вижу несколько случаев ввода строк 8 Go ... но все же,лучше быть правильным ...)

Edit 2

Два (так называемые C и C ++) коды различны. "C-код" ожидает, что идентификаторы и имена имеют длину меньше, чем5, или программа существует с ошибкой. «Код C ++» не имеет такого ограничения. Тем не менее, это ограничение является основанием для массовогоОптимизация, если вы подтвердите, что имена и идентификаторы всегда меньше 5 символов.

3 голосов
/ 21 октября 2010

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

3 голосов
/ 21 октября 2010

Измените размер вектора на достаточно большой, прежде чем начинать заполнять его. Или используйте указатели на строки вместо строк.

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

И идентификатор и имя должны быть string вместо char* и инициализироваться следующим образом (при условии, что вы все еще используете string вместо string*):

id = string(lineBuffer, lineBuffer + l);
...
name = string(lineBuffer + s, lineBuffer + s + l);
...
ids_.push_back(id);
names_.push_back(name);
2 голосов
/ 22 октября 2010

потоков позаботятся о большом количестве тяжелой работы для вас.Прекратите все делать сами, и позвольте библиотеке помочь вам:

void converter::updateNewList(){
    std::ifstream NewList ("NewList.txt");

    while(NewList.good()){
        int id (0);
        std::string name;

        // get the ID and name
        NewList >> id >> name;

        // ignore the rest of the line
        NewList.ignore( numeric_limits<streamsize>::max(), '\n');

        ids_.push_back (id);
        names_.push_back(name);
    }

    NewList.close();
}

Нет необходимости выполнять разбивку пробелов вручную.

Кроме того, вы можете найти этот сайт полезной ссылкой:http://www.cplusplus.com/reference/iostream/ifstream/

2 голосов
/ 21 октября 2010

Я считаю, что главная проблема здесь в том, что ваша строковая версия копирует вещи дважды - сначала в динамически размещенные char[], называемые name и id, а затем в std::string с, а ваша vector<char *> версия. вероятно, не делает этого. Чтобы ускорить строковую версию, вам нужно прочитать непосредственно в строки и избавиться от всех избыточных копий

2 голосов
/ 21 октября 2010

Вы можете попытаться reserve количество vector значений, чтобы уменьшить количество выделений (которые являются дорогостоящими), как сказал Диалектик (вероятно, от древних цыган?).

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

В C строки (которых, скажем, не существует - у них нет контейнера из библиотеки, такой как STL), требуют больше работы, но, по крайней мере, мы знаем, что происходит, когда имеем дело сих.В STL каждая удобная операция (то есть, требующая меньше работы от программиста) может фактически потребовать много операций за сценой, в пределах класса string, в зависимости от того, как вы ее используете.

Таким образом, хотя выделения / освобождения являются дорогостоящим процессом, остальная логика, особенно строковый процесс, возможно / должна быть рассмотрена также.

2 голосов
/ 21 октября 2010

За исключением std :: string, это программа на языке Си.

Попробуйте использовать fstream и использовать профилировщик для определения горлышка бутылки.

1 голос
/ 21 октября 2010

Вы можете использовать профилировщик, чтобы узнать, где ваш код потребляет больше всего времени.Если вы, например, используете gcc, вы можете скомпилировать вашу программу с -pg.При запуске он сохраняет результаты профилирования в файл.Вы можете запустить gprof в двоичном файле, чтобы получить удобочитаемые результаты.Как только вы узнаете, на что тратится больше всего времени, вы можете опубликовать этот фрагмент кода для дальнейших вопросов.

...