Упаковка и распаковка в .NET - Блог ITVDN
ITVDN: курсы программирования
Видеокурсы по
программированию

    Выбери свою IT специальность

    Начать бесплатно

    Выбери свою IT специальность

    Начать бесплатно

      Упаковка и распаковка в .NET

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

       

      Что такое упаковка и распаковка?

      Зачем нам задумываться об упаковке и распаковке? Разве это не обязанность .NET-среды, которая следит за управлением данных и, соответственно, сама "выбирает" наиболее оптимальный способ их хранения?

      На самом деле - нет. Что очень важно знать и понимать -  так это механизм перемещения данных из области стека в кучу - и наоборот.

      Помните:

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

      К примеру, здесь мы имеем следующий пример упаковки:

      А вот состояние памяти в момент произведения операции:

      Чтобы сохранить значение "123" в виде объекта, в куче создается "упаковка", куда впоследствии и перемещаются данные.

      Когда же производится распаковка:

      Вот что происходит с памятью:

      Значение "123" было изъято из упаковки и помещено назад в область стека.

      Заметьте, что когда тип данных i упаковывается внутри объекта o, в стеке хранится лишь ссылка, в то время как само значение хранится в куче. Как только производиться распаковка, данные в куче обязаны быть скопированы в стек (переменная j). В обоих случаях наша цель - это работать с тем самым значением (123).

      Как вы можете себе представить, сии операции могут быть достаточно ресурсоемкими.

       

      Давайте рассмотрим IL

      Когда мы производим подобный анализ производительности, часто бывает полезно заглянуть непосредственно в Intermediate Language (IL).

      Мы еще не рассматривали эту концепцию, но, как вы наверняка знаете, когда мы производим компиляцию в DLL или EXE, выходной файл на самом деле содержит IL - промежуточный код, который в последствии исполняется JIT и впоследствии - виртуальной машиной. Среда выполнения .NET обязана как-то знать, нужно ли упаковывать или распаковывать определенные переменные. Поэтому для обозначения этих операций также требуются дополнительные затраты памяти.

      Давайте создадим несложное .NET консольное приложение:

      Теперь скомпилируем приложение и при помощи утилитки ILSpy посмотрим его код внутри EXE.

      Как только EXE-файл будет открыт в ILSpy, пронавигируемся к методу Main, выбрав "IL with C#".

      Заметьте, что операция box выполняется только после присвоение ссылочному типу значения значимого. И наоборот: unbox.any - только после попытки присвоить ссылочному типу данных значимой переменной.

      Это де-факто способ, которым операции упаковки и распаковки представлены в IL.

       

      Когда стоит производить упаковку и распаковку?

      Код в примере выше скорее всего вам покажется наивным, и вы можете подумать: "Эй, что за вздор! Я никогда не буду такого делать". Что же, в большинстве случаев это действительно так. Но данные в нашем приложении часто упаковываются и распаковываются, когда мы об этом даже не догадываемся.

       

      Гетерогенные коллекции

      К примеру, старая школа до сих пор может похвастаться ArrayList.

      Метод добавления элемента здесь, как можно отметить, принимает object-параметр.

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

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

       

      Конкатенация строк

      Другой интересный пример в виде конкатенации строк.

      Эта операция требует наличия метода String.Concat, который принимает два object-параметра.

      Дабы избежать подобных ситуаций, нам достаточно просто немного изменить код, используя на переменной типа int метод ToString (и здесь стоит проигнорировать сообщение ReSharper о том, что операция бессмысленна:) ).

      И все! Никакой упаковки больше нет.

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

       

      Производительность

      Как мы уже говорили, упаковка и распаковка требуют определенных затрат производительности. В случае с конкатенацией строк, выигрыш от применения ToString весьма незначителен. Именно потому, как я упомянул выше, даже ReSharper не советовал нам делать подобное:

      В этом случае гораздо лучше сохранить читабельность кода без ToString.

      Целесообразность оптимизации появляется, как правило, тогда, когда операции упаковки и распаковки предстоит производить в цикле сотни и тысячи раз. В этом случае время выполнения кода с упаковкой может составлять порядка 150 процентов от времени исполнения кода без нее (вы можете сами создать тестовое приложение и сравнить требуемый промежуток времени).

      Упакованные значения могут также требовать больше памяти, чем значения в стеке. Копирование значений в/из стека также требует своих затрат. Согласно MSDN, упаковка может занимать порядка 20 раз больше времени, нежели простое присвоение. В то время как распаковка примерно в 4 раза медленней простого присвоения.

       

      Итак... зачем же тогда вообще нужно использовать упаковку и распаковку?

      Несмотря на все недостатки в плане падения производительности .NET -приложения, концепции упаковки и распаковки были внедрены в .NET не просто так. И вот причины:

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

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

       

      Подведем итоги

      В сегодняшнем уроке мы рассмотрели, что такое упаковка и распаковка, как она представлена в IL-коде, и какое влияние на производительность они имеют. Искренне надеюсь, моя статья сумела прояснить некоторые общие концепции, хотя бы чуть-чуть. :)

      В грядущих статьях мы рассмотрим механизм сборки мусора. Если у вас есть идеи или пожелания касательно материала новых статьей - милости просим в комментарии!

       

      Автор перевода: Евгений Лукашук

      Источник

      КОММЕНТАРИИ И ОБСУЖДЕНИЯ

      Пакеты подписки с доступом ко всем курсам и сервисам

      Стартовый
      • Все видеокурсы на 3 месяца
      • Тестирование по 10 курсам
      • Проверка 5 домашних заданий
      • Консультация с тренером 30 мин
      Базовый
      • Все видеокурсы на 6 месяцев
      • Тестирование по 16 курсам
      • Проверка 10 домашних заданий
      • Консультация с тренером 60 мин
      Премиум
      • Все видеокурсы на 1 год
      • Тестирование по 24 курсам
      • Проверка 20 домашних заданий
      • Консультация с тренером 120 мин
      Notification success
      Мы используем cookie-файлы, чтобы сделать взаимодействие с нашими веб-сайтами и услугами простым и значимым.