Подтвердить что ты не робот

Управление строковой памятью С++

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

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

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

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

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

Но до этого, какие-либо советы от вас, эксперты С++?

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

4b9b3361

Ответ 1

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

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

Ответ 3

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

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

используйте std::string.

EDIT:

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

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

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

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

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

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

Вам нужно профайл. Это катастрофа.

Ответ 4

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

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, чтобы найти начало следующей строки.

Ответ 5

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

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

Ответ 6

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

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

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

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

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

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

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

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

Ответ 7

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

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

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

Ответ 8

Загрузите строку в один буфер, проанализируйте текст, чтобы заменить разрывы строк на терминаторы строк ('\ 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 минут, и некоторые тесты звучат хорошо:)

Ответ 9

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

Ответ 10

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