C ++ строковое управление памятью - PullRequest
12 голосов
/ 04 марта 2009

На прошлой неделе я написал несколько строк кода на C # для запуска большого текстового файла (300 000 строк) в словарь. Написание заняло десять минут, и оно было выполнено менее чем за секунду.

Теперь я конвертирую этот кусок кода в C ++ (потому что он мне нужен в старом COM-объекте C ++). Я провел два дня на этом далеко. :-( Хотя разница в производительности сама по себе шокирующая, мне нужно несколько советов по поводу производительности.

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

Есть ли шанс, что я смогу выделить столько строк, не увидев такого ужасного снижения производительности?

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

Но до этого есть какой-нибудь совет от ваших экспертов по C ++?

РЕДАКТИРОВАТЬ : Мой ответ сам себе дан ниже. Я понял, что это самый быстрый маршрут для меня, а также шаг в том, что I считает правильным направлением - к более управляемому коду.

Ответы [ 10 ]

12 голосов
/ 04 марта 2009

Это звучит очень похоже на то, как испортилась производительность китайско-английского словаря C ++ и C # Рэймонда Чена против Rico Mariani. Рэймонду потребовалось несколько итераций, чтобы победить C #.

Возможно, есть идеи, которые могли бы помочь.

http://blogs.msdn.com/ricom/archive/2005/05/10/performance-quiz-6-chinese-english-dictionary-reader.aspx

11 голосов
/ 04 марта 2009

Вы вступаете в туфли Раймонда Чена. Он сделал то же самое, написав китайский словарь на неуправляемом C ++. Рико Мариани тоже сделал, написав это на C #. Мистер Мариани сделал одну версию. Г-н Чэнь написал 6 версий, пытаясь соответствовать духу версии Мариани. В значительной степени он переписал значительную часть библиотеки времени выполнения C / C ++.

После этого управляемый код получил гораздо большее уважение. Распределитель GC невозможно победить. Проверьте этот блог сообщение для ссылок. Это сообщение в блоге может вас заинтересовать, поучительно посмотреть, как семантика значения STL является частью проблемы.

10 голосов
/ 04 марта 2009

Хлоп. избавиться от CStrings ...

попробуйте и профилировщик. Вы уверены, что не просто запускаете отладочный код?

вместо этого используйте std :: string.

EDIT:

Я только что сделал простой тест сравнения ctor и dtor.

CStringW, кажется, занимает от 2 до 3 раз больше времени для создания нового / удаления.

повторяется 1000000 раз, делая new / delete для каждого типа. Ничего другого - и вызов GetTickCount () до и после каждого цикла. Последовательно получайте вдвое больше времени для CStringW.

Это не решает всю вашу проблему, хотя я подозреваю.

EDIT: Я также не думаю, что проблема заключается в использовании строки или CStringW - что-то еще вызывает вашу проблему.

(но ради бога, все равно используйте stl!)

Вы должны профилировать его. Это катастрофа.

4 голосов
/ 04 марта 2009

Если это словарь только для чтения, то для вас должно работать следующее:

Use fseek/ftell functionality, to find the size of the text file.

Allocate a chunk of memory of that size + 1 to hold it.

fread the entire text file, into your memory chunk.

Iterate though the chunk.

    push_back into a vector<const char *> the starting address of each line.

    search for the line terminator using strchr.

    when you find it, deposit a NUL, which turns it into a string.
    the next character is the start of the next line

until you do not find a line terminator.

Insert a final NUL character.

Теперь вы можете использовать вектор, чтобы получить указатель, который позволит вам получить доступ к соответствующему значению.

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

[EDIT] Это может быть немного сложнее на платформе dos, так как терминатором строки является CRLF.

В этом случае используйте strstr, чтобы найти его, и увеличьте на 2, чтобы найти начало следующей строки.

3 голосов
/ 09 мая 2009

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

Напишите свой собственный распределитель - выделите большой кусок памяти, а затем просто выделите в нем указатель при выделении. Это то, что на самом деле делает распределитель .NET. Когда вы будете готовы, удалите весь буфер.

Я думаю, что был пример написания пользовательских операторов new / delete в (Подробнее) Effective C ++

3 голосов
/ 04 марта 2009

Спасибо всем за ваши проницательные комментарии. Upvotes для вас! : -)

Должен признать, что я совсем не был готов к этому - что C # таким образом выбьет живое дерьмо из старого доброго C ++. Пожалуйста, не читайте это как оскорбление для C ++, но вместо этого какой удивительно хороший менеджер памяти, который находится внутри .NET Framework.

Я решил сделать шаг назад и сражаться в этой битве на арене InterOp! То есть я сохраню свой код C # и позволю своему старому коду C ++ общаться с кодом C # через интерфейс COM.

Было задано много вопросов о моем коде, и я постараюсь ответить на некоторые из них:

  • Компилятором была Visual Studio 2008, и нет, я не запускал отладочную сборку.

  • Файл был прочитан с помощью программы чтения файлов UTF8, которую я скачал у сотрудника Microsoft, который опубликовал его на своем сайте. Он вернул CStringW и около 30% времени было потрачено на чтение файла.

  • Контейнер, в котором я хранил строки, был просто вектором фиксированного размера указателей на CStringW, и его размер никогда не изменялся.

РЕДАКТИРОВАТЬ: Я убежден, что предложения, которые мне дали, будет действительно работать, и что я, вероятно, мог бы побить код C #, если бы я потратил на это достаточно времени. С другой стороны, это не принесло бы никакой пользы для клиента, и единственная причина, по которой это можно было бы сделать, это просто доказать, что это можно сделать ...

3 голосов
/ 04 марта 2009

В каком контейнере вы храните свои строки? Если это std::vector из CStringW и если у вас заранее не хватает reserve памяти, вы обязательно должны получить удар. vector обычно изменяет размер, как только достигает своего предела (который не очень высок), а затем копирует полностью в новую область памяти, которая может дать вам большой успех. Поскольку ваш vector растет в геометрической прогрессии (то есть, если начальный размер равен 1, в следующий раз, когда в следующий раз он выделит 2, 4, попадание станет все менее и менее частым).

Это также помогает узнать, как долго отдельные строки. (Время от времени:)

2 голосов
/ 04 марта 2009

Загрузить строку в один буфер, проанализировать текст, чтобы заменить разрывы строк на разделители строк ('\ 0'), и использовать указатели в этом буфере для добавления в набор.

В качестве альтернативы - например, если вам нужно выполнить преобразование ANSI / UNICODE во время загрузки - используйте распределитель чанков, который жертвует удалением отдельных элементов.

class ChunkAlloc
{
   std::vector<BYTE> m_data;
   size_t m_fill;
   public:
     ChunkAlloc(size_t chunkSize) : m_data(size), m_fill(0) {}
     void * Alloc(size_t size)
     {
       if (m_data.size() - m_fill < size)
       {
          // normally, you'd reserve a new chunk here
          return 0;
       }
       void * result = &(m_data[m_fill]);
       m_fill += size;
       return m_fill;
     }
}
// all allocations from chuunk are freed when chung is destroyed.

Не взломать это вместе через десять минут, но через 30 минут и некоторые тесты звучат хорошо:)

1 голос
/ 04 марта 2009

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

0 голосов
/ 29 декабря 2014

Неудивительно, что управление памятью в CLR лучше, чем куча старых и грязных трюков, на которых основан MFC: он как минимум в два раза моложе самого MFC и основан на пуле. Когда мне приходилось работать над аналогичным проектом со строковыми массивами и WinAPI / MFC, я просто использовал std :: basic_string, созданный с помощью WinCHI TCHAR, и мой собственный распределитель, основанный на Loki :: SmallObjAllocator. В этом случае вы также можете взглянуть на boost :: pool (если вы хотите, чтобы он имел «std feel» или вам пришлось использовать версию компилятора VC ++ старше 7.1).

...