Управление строковой памятью С++
На прошлой неделе я написал несколько строк кода в С#, чтобы запустить большой текстовый файл (300 000 строк) в словарь. Потребовалось десять минут, чтобы написать, и это выполнено менее чем за секунду.
Теперь я конвертирую эту часть кода в С++ (потому что мне это нужно в старом объекте С++ COM). Я провел там два дня.:-( Несмотря на то, что разница в производительности просто потрясает сама по себе, это производительность, о которой мне бы понадобился совет.
Загрузка занимает 7 секунд и еще хуже: для освобождения всех CStringWs требуется ровно столько времени. Это неприемлемо, и я должен найти способ повысить производительность.
Есть ли вероятность, что я могу выделить это множество строк, не видя этого ужасного ухудшения производительности?
Мое предположение прямо сейчас заключается в том, что мне придется набивать весь текст в большой массив, а затем пусть моя хэш-таблица указывает на начало каждой строки в этом массиве и отбрасывает материал CStringW.
Но до этого, какие-либо советы от вас, эксперты С++?
РЕДАКТИРОВАТЬ. Мой ответ на вопрос приводится ниже. Я понял, что это самый быстрый путь для меня, а также шаг в том, что я считаю правильным направлением - к более управляемому коду.
Ответы
Ответ 1
Вы ступаете в обувь Раймонда Чена. Он сделал то же самое, написав китайский словарь в неуправляемом С++. Рико Мариани тоже сделал это, написав его на С#. Г-н Мариани сделал одну версию. Г-н Чэнь написал 6 версий, пытаясь соответствовать перформам версии Mariani. Он почти переписал значительные куски библиотеки времени выполнения C/С++, чтобы добраться туда.
После этого управляемый код получил больше уважения. ГЦ-распределитель невозможно избить. Отметьте этот блог для ссылок. Это сообщение blog post может вас заинтересовать, поучительно посмотреть, как семантика значения STL является частью проблемы.
Ответ 2
Это звучит очень похоже на то, как игра Raymond Chen vs Rico Mariani С++ vs С# китайского/английского словаря испепеляла. Потребовалось несколько итераций Раймонда, чтобы победить С#.
Возможно, есть идеи, которые помогут.
http://blogs.msdn.com/ricom/archive/2005/05/10/performance-quiz-6-chinese-english-dictionary-reader.aspx
Ответ 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).