Оригинальная книга была полностью пересмотрена как технически, так и путем улучшения «стиля» для облегчения чтения. Устаревшие части были удалены и теперь доступны бесплатно на https://prodotnetmemory.com/. Особые усилия были приложены для представления и использования набора инструментов .NET CLI для Windows и Linux в реальных сценариях. Также объяснены дополнительные недокументированные параметры конфигурации.
Во втором издании рассматривается больше сред выполнения .NET, включая .NET Framework 4.7+, .NET Core 3.0, 3.1, .NET 5 до 8. Ниже приведен краткий обзор основных дополнений и улучшений, внесенных в это издание:
В информатике память всегда была – от перфокарт и магнитных лент до современных сложных чипов DRAM. И она всегда будет там, возможно, в форме научно-фантастических голографических чипов или даже гораздо более удивительных вещей, которые мы сейчас не можем себе представить. Конечно, память была там не без причины. Хорошо известно, что компьютерные программы – это алгоритмы и структуры данных, объединенные вместе. Мне очень нравится это предложение. Наверное, каждый хотя бы раз слышал о книге «Алгоритмы + Структуры данных = Программы», написанной Никлаусом Виртом (Prentice Hall, 1976), где было придумано это замечательное предложение.
С самого начала развития сферы разработки программного обеспечения управление памятью было темой, известной своей важностью. С первых компьютерных машин инженерам приходилось думать о хранении алгоритмов (программного кода) и структур данных (программных данных). Всегда было важно, как и где эти данные загружаются и хранятся для последующего использования.
В этом аспекте программная инженерия и управление памятью всегда были неразрывно связаны, так же как программная инженерия и алгоритмы. И я верю, что так будет всегда. Память - это ограниченный ресурс, и так будет всегда. Следовательно, в какой-то момент или в какой-то степени память всегда будет храниться в умах будущих разработчиков. Если ресурс ограничен, всегда может быть какая-то ошибка или неправильное использование, которые приведут к истощению этого ресурса. Память здесь не является исключением.
Сказав это, есть, безусловно, одна вещь, которая постоянно меняется в управлении памятью - количество. Первые разработчики, или мы должны называть их инженерами, знали каждый бит своих программ. Затем у них были килобайты памяти. С каждым десятилетием эти цифры растут, и сегодня мы живем во времена гигабайтов, в то время как терабайты и петабайты любезно стучатся в дверь, ожидая своей очереди. По мере роста размера памяти время доступа уменьшается, что позволяет обрабатывать все эти данные за удовлетворительное время. Но даже если мы можем сказать, что память быстрая, простые алгоритмы управления памятью, которые пытаются обрабатывать все гигабайты данных без каких-либо оптимизаций и более сложных настроек, были бы невозможны. Это в основном потому, что время доступа к памяти улучшается медленнее, чем вычислительная мощность процессоров, использующих их. Необходимо проявлять особую осторожность, чтобы не создавать узкие места доступа к памяти, ограничивая мощность современных процессоров.
Это делает управление памятью не только критически важным, но и действительно увлекательной частью компьютерной науки. Автоматическое управление памятью делает его еще лучше. Это не так просто, как сказать «пусть неиспользуемые объекты будут освобождены». Что, как и когда — эти простые аспекты управления памятью делают его постоянно текущим процессом улучшения старых и изобретения новых алгоритмов. Бесчисленные научные работы и докторские диссертации рассматривают, как автоматически управлять памятью наиболее оптимальным способом. Такие мероприятия, как Международный симпозиум по управлению памятью (ISMM), каждый год показывают, как много сделано в этой области, в отношении сборки мусора; динамического выделения; и взаимодействия со средами выполнения, компиляторами и операционными системами. И затем академические исследования немного преобразуются в коммерческие и открытые продукты, которые мы используем в повседневной работе.
.NET — это прекрасный пример управляемой среды, где вся эта сложность скрыта под ней, доступная разработчикам как приятная, готовая к использованию платформа. И действительно, мы можем использовать ее, не осознавая глубинной сложности, что является большим достижением .NET в целом. Однако чем больше производительность осознается нашей программой, тем меньше вероятность избежать получения каких-либо знаний о том, как и почему все работает под ней. Более того, лично я считаю, что просто забавно знать, как работают вещи, которые мы используем каждый день!
Я написал эту книгу так, как мне бы хотелось ее прочитать много лет назад — когда я только начал свой путь в области производительности и диагностики .NET. Таким образом, эта книга не начинается с типичного введения о куче и стеке или описания нескольких поколений. Вместо этого я начинаю с самых основ управления памятью в целом. Другими словами, я попытался написать эту книгу таким образом, чтобы вы почувствовали эту очень интересную тему, а не только показали «вот сборщик мусора .NET, и он делает то-то и то-то». Предоставление информации не только о том, что, но и о том, как, и, что еще важнее, почему — должно действительно помочь вам понять, что находится за кулисами управления памятью .NET. Следовательно, все, что вы прочтете по этой теме в будущем, должно быть для вас более понятным. Я пытаюсь просветить вас знаниями немного более общими, чем просто относящиеся к .NET, особенно в первых двух главах. Это приводит к более глубокому пониманию темы, которое зачастую может быть применено и к другим задачам программной инженерии (благодаря пониманию алгоритмов, структур данных и просто хорошим инженерным навыкам).
т того, насколько вы опытны, вы должны найти здесь что-то интересное. Хотя мы начинаем с основ, начинающие программисты быстро получат возможность глубже погрузиться во внутренние механизмы .NET. Более продвинутые программисты найдут многие детали реализации более интересными. И, прежде всего, независимо от опыта, каждый должен иметь возможность извлечь пользу из представленных практических примеров кода и диагностики проблем.
Таким образом, знания из этой книги должны помочь вам писать лучший код — более производительный и осознающий память, использующий связанные функции без страха, но с полным пониманием. Это также приводит к лучшей производительности и масштабируемости ваших приложений — чем больше ориентирован ваш код на память, тем меньше он подвержен узким местам ресурсов и их неоптимальному использованию. Надеюсь, вы найдете подзаголовок «Для лучшего кода, производительности и масштабируемости» оправданным после прочтения этой книги.
екущего состояния фреймворка .NET и его внутренностей. Независимо от того, как будут развиваться будущие фреймворки .NET, я считаю, что большая часть знаний в этой книге будет действительно верна в течение долгого времени. Даже если некоторые детали реализации изменятся, вы сможете легко понять их благодаря знаниям из этой книги. Просто потому, что базовые принципы не будут меняться так быстро. Желаю вам приятного путешествия по огромной и увлекательной теме автоматического управления памятью!
Сказав это, я хотел бы также подчеркнуть несколько вещей, которые не особо представлены в этой книге. Тема управления памятью, хотя она и кажется очень специализированной и узкой на первый взгляд, на самом деле удивительно широка. Хотя я затрагиваю много тем, они иногда представлены не так подробно, как мне бы хотелось, из-за нехватки места. Даже с такими ограничениями книга составляет около 1104 страниц! К этим пропущенным темам относятся, например, исчерпывающие ссылки на другие управляемые среды (такие как Java, Python или Ruby). Я также извиняюсь перед поклонниками F# за столь мало ссылок на этот язык. Для основательного описания просто не хватило страниц, и я не хотел публиковать что-либо неполным. Я также хотел бы уделить гораздо больше внимания среде Linux, но это настолько свежо и не охвачено темой инструментов, что на момент написания я даю вам только некоторые предложения в Главе 3 (и полностью опускаю мир macOS по тем же причинам). Очевидно, я также опустил большую часть других, не связанных напрямую с памятью аспектов производительности в .NET, например, темы многопоточности.
Во-вторых, хотя я сделал все возможное, чтобы представить практические применения обсуждаемых тем и техник, это не всегда возможно, не делая это совершенно утомительным образом. Практического применения просто слишком много. Я скорее ожидаю от читателя всестороннего чтения, переосмысления темы и применения полученных знаний в своей обычной работе. Поймите, как что-то работает, и вы сможете этим пользоваться!
Это особенно касается так называемых сценариев. Обратите внимание, что все сценарии, включенные в эту книгу, предназначены для иллюстративных целей. Их код был сокращен до минимума, чтобы легче показать первопричину одной-единственной проблемы. Могут быть и другие причины, стоящие за наблюдаемым неправильным поведением (например, множество способов, которыми можно заметить утечки управляемой памяти). Сценарии были подготовлены таким образом, чтобы помочь проиллюстрировать такие проблемы с помощью одного примера причины, поскольку, очевидно, невозможно включить все вероятные причины в одну книгу. Более того, в реальных сценариях ваше расследование будет загромождено большим количеством шумных данных и ложных путей расследования. Часто не существует единого способа решения описанных проблем, но есть много способов, как вы можете найти первопричину во время анализа проблем. Это делает такое устранение неполадок смесью чисто инженерной задачи с небольшим количеством искусства, подкрепленного вашей интуицией. Обратите внимание также, что сценарии иногда ссылаются друг на друга, чтобы не повторяться снова и снова с теми же шагами, рисунками и описаниями.
Я специально воздержался от упоминания различных технологических специфических случаев и источников проблем в этой книге. Они просто… слишком технологически специфические. Если бы я писал эту книгу 10 лет назад, мне, вероятно, пришлось бы перечислить различные типичные сценарии утечек памяти в ASP.NET WebForms и WinForms. Несколько лет назад? ASP.NET MVC, WPF, WCF, WF,… Сейчас? ASP.NET Core, EF Core, Azure Functions, что еще? Надеюсь, вы поняли. Такие знания слишком быстро устаревают. Книга, напичканная примерами утечек памяти WCF, вряд ли заинтересует кого-то сегодня. Я большой поклонник поговорки: «Дай человеку рыбу — и ты накормишь его на сегодня. Научи человека ловить рыбу — и ты накормишь его на всю жизнь». Таким образом, все знания в этой книге, все сценарии учат вас ловить рыбу. Все проблемы, независимо от базовой конкретной технологии, можно диагностировать одинаково, если применить достаточно знаний и понимания.
Все это также делает чтение этой книги довольно требовательным, так как она иногда полна подробностей и, возможно, немного подавляющего объема информации. Несмотря ни на что, я призываю вас читать вдумчиво и медленно, сопротивляясь искушению только беглого чтения. Например, чтобы в полной мере воспользоваться этой книгой, следует внимательно изучить показанный код и представленные рисунки (а не просто смотреть на них, заявляя, что они очевидны, поэтому их можно легко пропустить).
Мы живем в прекрасное время среды выполнения CoreCLR с открытым исходным кодом. Это выводит возможности понимания среды выполнения CLR на совершенно новый уровень. Нет никаких догадок, никаких загадок. Все находится в коде, может быть прочитано и понято. Таким образом, мои исследования того, как все работает, в значительной степени основаны на коде CoreCLR его GC (который также используется совместно с .NET Framework). Я провел бесчисленное количество дней и недель, анализируя этот огромный объем хорошей инженерной работы. Я думаю, что это здорово, и я верю, что есть люди, которые также хотели бы изучить известный файл gc.cpp размером в несколько десятков тысяч строк кода. Однако у него очень крутая кривая обучения. Чтобы помочь вам в этом, я часто оставляю некоторые подсказки, с чего начать изучение кода CoreCLR относительно описанных тем. Не стесняйтесь получить еще более глубокое понимание из пунктов gc.cpp, которые я предлагаю!
После прочтения этой книги вы сможете:
Что касается содержания самой книги, то оно выглядит следующим образом. Глава 1 представляет собой очень общее теоретическое введение в управление памятью, практически без ссылок на .NET в частности. Глава 2 также представляет собой общее введение в управление памятью на уровне оборудования и операционной системы. Обе главы можно рассматривать как важное, но необязательное введение. Они дают полезный, более широкий взгляд на тему, полезный в остальной части книги. Хотя я, очевидно, и настоятельно рекомендую вам прочитать их, вы можете пропустить их, если вы торопитесь или вас интересуют только самые практические темы, связанные с .NET. Примечание для продвинутых читателей — даже если вы думаете, что темы из этих двух первых глав вам хорошо известны, пожалуйста, прочтите их. Я постарался включить туда не только очевидную информацию, которая может показаться вам интересной.
Глава 3 посвящена исключительно измерениям и различным инструментам (среди которых некоторые очень часто используются далее в книге). Это чтение, которое содержит в основном список инструментов и как их использовать. Если вас интересует в основном теоретическая часть книги, вы можете только бегло просмотреть ее. С другой стороны, если вы планируете интенсивно использовать знания этой книги при диагностике проблем, вы, вероятно, будете часто возвращаться к этой главе.
Глава 4 — первая, в которой мы начинаем интенсивно говорить о .NET, при этом все еще в общем плане, что позволяет нам понять некоторые важные внутренние элементы, такие как система типов .NET (включая тип значения против ссылочного типа), интернирование строк или статические данные. Если вы действительно торопитесь, вы можете начать читать оттуда. Глава 5 описывает первую действительно связанную с памятью тему — как организована память в приложениях .NET, вводя концепцию кучи малых и больших объектов, а также сегментов. Глава 6 углубляется во внутренние элементы, связанные с памятью, и посвящена исключительно выделению памяти. Довольно удивительно, что довольно большая глава может быть посвящена такой теоретически простой теме. Важная и большая часть этой главы — описание различных источников выделения в контексте их избегания.
Главы с 7 по 10 являются основными частями, описывающими, как работает GC в .NET, с практическими примерами и соображениями, вытекающими из таких знаний. Чтобы не перегружать слишком большим количеством информации, предоставляемой одновременно, эти главы описывают простейшую разновидность GC — так называемую Workstation Non-Concurrent. С другой стороны, глава 11 посвящена описанию всех других разновидностей с всесторонними соображениями, которые можно выбрать. Глава 12 завершает часть книги, посвященную GC, описывая три важных механизма: финализацию, утилизируемые объекты и слабые ссылки.
Три последние главы составляют «продвинутую» часть книги, в том смысле, что они объясняют, как все работает за пределами основной части управления памятью .NET. Глава 13 объясняет, например, тему управляемых указателей и углубляется в структуры (включая недавно добавленные структуры ссылок). Глава 14 уделяет много внимания типам и методам, которые в последнее время набирают все большую популярность, таким как типы Span<T> и Memory<T>. Также есть умный раздел, посвященный не столь известной теме проектирования, ориентированного на данные, и несколько слов о входящих функциях C# (таких как ссылочные типы, допускающие значение null, и конвейеры). Глава 15, последняя, описывает различные способы управления и мониторинга GC из кода, включая API класса GC, хостинг CLR или библиотеку ClrMD.
Большинство листингов из этой книги доступны в сопутствующем репозитории GitHub по адресу https://github.com/Apress/pro-.net-memory. Он организован в главы, и большинство из них содержат два решения: одно для проведенных бенчмарков и одно для других листингов. Обратите внимание, что хотя включенные проекты содержат листинги, часто есть больше кода, на который можно посмотреть. Если вы хотите использовать или поэкспериментировать с определенным листингом, самым простым способом будет просто найти его номер и поиграть с ним и его использованием. Но я также рекомендую вам просто поискать в проектах определенные темы для лучшего понимания.
Не так уж много важных соглашений я хотел бы здесь упомянуть. Самое важное — это разграничить два основных понятия, используемых в остальной части книги:
Эта книга также довольно самодостаточна и не ссылается на многие другие материалы или книги. Очевидно, что существует много отличных знаний, и мне нужно будет ссылаться на различные источники много раз. Вместо этого позвольте мне просто перечислить предлагаемые книги и статьи по моему выбору в качестве дополнительного источника знаний:
Также есть огромное количество знаний из различных онлайн-блогов и статей. Но вместо того, чтобы заваливать эти страницы их списком, позвольте мне просто перенаправить вас на отличный https://github.com/adamsitnik/awesome-dot-net-performance репозиторий, поддерживаемый Адамом Ситником.