Узкое место в распределении / выделении памяти? - PullRequest
45 голосов
/ 22 января 2009

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

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

Редактировать: Хотя я упоминаю malloc, этот вопрос не предназначен для C / C ++.

Ответы [ 12 ]

36 голосов
/ 22 января 2009

Это важно, особенно с ростом фрагментации, и распределителю приходится интенсивно охотиться в больших кучах для смежных областей, которые вы запрашиваете. Большинство чувствительных к производительности приложений обычно пишут свои собственные распределители блоков фиксированного размера (например, они запрашивают у ОС память по 16 МБ за раз, а затем разбивают ее на фиксированные блоки по 4 КБ, 16 КБ и т. Д.), Чтобы избежать этой проблемы.

В играх, которые я видел, вызовы malloc () / free () потребляют до 15% ЦП (в плохо написанных продуктах) или с тщательно написанными и оптимизированными распределителями блоков - всего 5%. Учитывая, что игра должна иметь постоянную пропускную способность в шестьдесят герц, ее остановка на 500 мс, а сборщик мусора иногда запускается, нецелесообразна.

20 голосов
/ 22 января 2009

Почти каждое высокопроизводительное приложение теперь должно использовать потоки для использования параллельных вычислений. Вот тут-то и возникает реальный фактор снижения скорости выделения памяти при написании приложений на C / C ++.

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

Java и C # лучше в этом, потому что потоки были разработаны с самого начала, а распределители памяти работают из пулов для каждого потока. Это можно сделать и в C / C ++, но это не происходит автоматически.

11 голосов
/ 22 января 2009

Прежде всего, поскольку вы сказали malloc, я предполагаю, что вы говорите о C или C ++.

Распределение и освобождение памяти, как правило, являются существенным узким местом для реальных программ. Многое происходит "под капотом", когда вы выделяете или освобождаете память, и все это зависит от системы; память может быть фактически перемещена или дефрагментирована, страницы могут быть реорганизованы - нет никакого независимого от платформы способа узнать, как это повлияет. Некоторые системы (например, многие игровые приставки) также не выполняют дефрагментацию памяти, поэтому в этих системах вы начнете получать ошибки нехватки памяти по мере фрагментации памяти.

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

Однако нет двух способов - если у вас есть чувствительная ко времени программа C / C ++, выполнение большого количества выделения / освобождения памяти приведет к снижению производительности.

7 голосов
/ 22 января 2009

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

Теперь может возникнуть проблема с захватом очень больших кусков памяти. И захват, но не правильное избавление от памяти - это то, о чем я беспокоюсь.

В языках на основе Java и JVM новые объекты теперь очень, очень, очень быстрые.

Вот одна приличная статья парня, который знает свое дело, с некоторыми ссылками внизу на дополнительные ссылки: http://www.ibm.com/developerworks/java/library/j-jtp09275.html

5 голосов
/ 23 января 2009

В Java (и, возможно, в других языках с достойной реализацией GC) выделение объекта очень дешево. В SUN JVM требуется всего 10 циклов ЦП. Malloc в C / c ++ намного дороже, просто потому, что он должен делать больше работы.

Тем не менее, даже размещение объектов в Java очень дешево, так как для многих пользователей параллельного веб-приложения все еще могут возникнуть проблемы с производительностью, потому что будет запущено больше сборщиков мусора. Поэтому существуют те косвенные затраты на выделение в Java, вызванные освобождением, выполняемым GC. Эти затраты трудно измерить количественно, потому что они очень сильно зависят от ваших настроек (сколько у вас памяти) и вашего приложения.

4 голосов
/ 06 июля 2009

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

Эта статья была написана в 2005 году, и управление памятью в стиле JVM было уже далеко впереди. С тех пор ситуация только улучшилась.

Какой язык может похвастаться более быстрым производительность распределения, Java язык или C / C ++? Ответ может удивить вас - выделение в современном JVM гораздо быстрее, чем лучшие выполнение реализаций malloc. общий путь к коду для нового объекта () в HotSpot 1.4.2 и более поздние версии примерно 10 машинных инструкций (данные предоставлены Sun; см. Ресурсы), в то время как самый эффективный malloc реализации в C требуют от в среднем между 60 и 100 инструкции на звонок (Detlefs, et. и др .; см. Ресурсы). И распределение производительность не является тривиальным компонентом общей производительности - ориентиры показать, что многие реальные C и C ++ программы, такие как Perl и Ghostscript, тратить от 20 до 30 процентов их общее время выполнения в malloc и бесплатно - гораздо больше, чем выделение и сборка мусора накладные расходы на здоровую Java применение.

3 голосов
/ 03 июня 2009

Я знаю, что отвечал ранее, однако, это было ответом на другой ответ, а не на ваш вопрос.

Если говорить с вами напрямую, если я правильно понимаю, ваши критерии использования производительности - это пропускная способность.

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

Ни одна из более ранних ссылок; IBM JVM paper, Microquill C, SUN JVM. Охватите этот момент, так что я очень подозреваю их применение сегодня, где, по крайней мере в AMD ABI, NUMA является выдающимся управляющим процессорами памяти.

Руки вниз; реальный мир, фальшивый мир, любой мир ... Технологии запроса / использования памяти с поддержкой NUMA работают быстрее. К сожалению, я сейчас использую Windows, и я не нашел "numastat", который доступен в Linux.

У моего друга * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - это '1020 *.

Несмотря на то, что я могу показывать время от времени, обычно ОЧЕНЬ большое количество запросов памяти локального узла поверх удаленного узла (подчеркивая очевидную производительность пропускная способность преимущество), Вы можете спокойно тестировать себя, и это, вероятно, будет то, что вам нужно сделать, так как ваша характеристика производительности будет очень специфичной.

Я знаю, что во многих отношениях, по крайней мере, более ранние версии 5.x VMWARE работали довольно плохо, по крайней мере в то время, потому что не использовали NUMA, часто требующие страниц от удаленного узла. Тем не менее, виртуальные машины являются уникальным зверем, когда дело доходит до компартментализации или контейнеризации памяти.

Одна из ссылок, которые я привел, касается реализации API Microsoft для AMD ABI, которая имеет специализированные интерфейсы выделения NUMA для использования разработчиками пользовательских наземных приложений;)

Вот довольно недавний анализ , визуальный и все, от некоторых разработчиков надстроек для браузеров, которые сравнивают 4 различных кучи. Естественно, тот, который они разработали , оказывается на вершине (странно, как люди, которые проводят тестирование, часто показывают самые высокие оценки).

Они в некотором смысле охватывают количественно, по крайней мере для их случая использования, какой точный компромисс существует между пространством / временем, как правило, они идентифицировали LFH (о да, и, кстати, LFH - это просто видимость стандартная куча) или подобный подход, по существу, потребляет значительно больше памяти, но со временем может закончиться, используя меньше памяти ... График тоже аккуратный ...

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

3 голосов
/ 23 января 2009

Здесь система распределения памяти c / c ++ работает лучше всего. Стратегия распределения по умолчанию подходит для большинства случаев, но ее можно изменить в соответствии с тем, что необходимо. В системах GC мало что можно сделать, чтобы изменить стратегии распределения. Конечно, есть цена, которую нужно заплатить, и это необходимость отслеживать распределение и правильно их освобождать. C ++ продвигает это дальше, и стратегию распределения можно указать для каждого класса с помощью оператора new:

class AClass
{
public:
  void *operator new (size_t size); // this will be called whenever there's a new AClass
   void *operator new [] (size_t size); // this will be called whenever there's a new AClass []
  void operator delete (void *memory); // if you define new, you really need to define delete as well
  void operator delete [] (void *memory);define delete as well
};

Многие из шаблонов STL также позволяют вам определять собственные распределители.

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

3 голосов
/ 22 января 2009

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

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

Эта концепция также используется большинством приложений, ориентированных на пропускную способность.

2 голосов
/ 02 февраля 2009

Согласно технической спецификации MicroQuill SmartHeap , «типичное приложение [...] тратит 40% своего общего времени выполнения на управление памятью». Вы можете принять эту цифру как верхнюю границу, я лично чувствую, что типичное приложение тратит больше, чем 10-15% времени выполнения, выделяя / освобождая память. Это редко является узким местом в однопоточном приложении.

В многопоточных приложениях C / C ++ стандартные распределители становятся проблемой из-за конфликта блокировок. Здесь вы начинаете искать более масштабируемые решения. Но имейте в виду Закон Амдала .

...