2009-03-03 2 views
12

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

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

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

есть ли шанс, что я могу выделить это много строк, не видя эту ужасную деградацию Performace?

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

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

EDIT: Мой ответ для себя приводится ниже. Я понял, что это самый быстрый маршрут для меня, а также шаг в том, что I рассмотреть в правильном направлении - в направлении более управляемого кода.

+0

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

+0

Вам определенно нужно предоставить дополнительную информацию о вашей структуре данных ... –

+1

Прежде чем я был знаком с .Net, переход от C++ к C# - не имея представления о том, как фреймворки (в моем случае VCL) были похожи или разные - имели бы было так же сложно, как и наоборот. Не нужно винить C++, чтобы задать свой вопрос. – overslacked

ответ

11

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

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

+0

Ну, процитировать Мариани «Итак, да, вы можете победить CLR». но я согласен, что производительность CLR очень впечатляет. –

3

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

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

+0

Фиксированный массив ...вы выделяете всю память вперед или изменяете размер массива с каждой новой строкой? –

+0

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

+0

Похоже, у вас все настроено идеально. Вы пытались использовать std :: wstring? Кроме того, пришло время воспользоваться помощью профилировщика. – dirkgently

10

Yikes. избавиться от CStrings ...

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

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

EDIT:

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

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

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

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

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

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

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

+0

Я попробую std :: string, следите за обновлениями! :-) –

+0

CString не так уж плохо ... – peterchen

+1

CString - это плохо. Во-первых, как я только что подтвердил, он медленнее, чем std :: string. Он не переносится. Это взлом MFC - вместе со всем другим мусором MFC. Не поймите меня неправильно, я использовал их в течение многих лет, но чем меньше MFC мы используем, тем лучше ... – Tim

1

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

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

+0

Это будет отлично работать и быстро. Я не думаю, что это необходимо, но он делает что-то плохое в своем коде, которое, вероятно, может быть исправлено и иметь читаемый и простой код. – Tim

+0

Вещь, которая убивает ее, - это затраты на строительство/разрушение с распределением/освобождением памяти. – EvilTeach

+0

Или (если Win32 поддерживает эквивалент) mmap файл MAP_PRIVATE (копирование при записи), что экономит его копирование. –

12

Это звучит очень похоже на то, как игра Raymond Chen vs Rico Mariani C++ vs C# китайского/английского словаря испепеляла. У Раймона было несколько итераций, чтобы обыграть C#.

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

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

+0

+1 для интересной ссылки – EvilTeach

+0

Да, вы не знаете, как вы правы! :-) Но для меня одна сложная вещь: я хочу читать ASCII, UTF8 и Unicode за один раз, поэтому я должен сделать перевод из данных файла в хранящиеся мной данные. –

+1

@ danbystrom, ASCII можно рассматривать как подмножество UTF-8. UTF-8 - это формат кодировки Unicode. Если у вас есть файлы в UTF-8, вы можете использовать любой символ, определенный в Unicode, а существующий текст ASCII не нуждается в повторном кодировании. – CMircea

2

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

+0

Да, да. Это то, что я выяснил и ночью, тоже! :-) –

+0

:) См. Раймонд против Рико (http://blogs.msdn.com/ricom/archive/2005/05/10/416151.aspx) очень похож на ваш проект с ясными результатами: С C++ вы можете решите свою проблему быстрее, чем приложение C#, даже загрузите, но оно вам стоит. Много. – peterchen

3

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

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

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

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

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

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

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

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

+0

спасибо за краткий обзор. – Tim

3

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

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

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

+0

, хотя я никогда не пытался это использовать, контейнеры стандартной библиотеки C++ принимают Allocator в качестве параметра, чтобы настроить/оптимизировать создание элементов. Если нужно. –

0

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

Смежные вопросы