что такое боксинг и анбоксинг
Boxing и unboxing — что быстрее?
Код примера доступен на github, поэтому приглашаю всех желающих сообщить о своих результатах измерений в комментариях.
Теория
Операция упаковки boxing характеризуется выделением памяти в управляемой куче (managed heap) под объект value type и дальнейшее присваивание указателя на этот участок памяти переменной в стеке.
Распаковка unboxing, напротив, выделяет память в стеке выполнения под объект, полученный из управляемой кучи с помощью указателя.
Казалось бы, в обоих случаях выделяется память и особой разницы быть не должно, если бы не одно но- крайне важной деталью является область памяти.
Как заметил blanabrother в комментариях, при выделении памяти/копировании значения в managed heap отсутствует процесс поиска свободного участка памяти и её возможная фрагментация ввиду инкриминирующегося указателя и дальнейшей её компактификации с использованием GC. Однако, опираясь на следующие измерения скорости выделения памяти в C++ посмею предположить, что область (тип) памяти является основной причиной такой разницы в производительности.
В случае же с распаковкой, память выделяется в стеке выполнения, который содержит указатель на свой конец, по совместительству являющийся началом участка памяти под новый объект.
Вывод из этого я делаю такой, что процесс упаковки должен занимать значительно больше времени, чем распаковки, ввиду возможных side effects связанных с GC и медленной скоростью выделения памяти/копирования значения в managed heap.
Практика
Для проверки этого утверждения я набросал 4 небольшие функции: 2 для boxing и 2 для unboxing типов int и struct.
Для замера производительности была использована библиотека BenchmarkDotNet в режиме Release (буду рад если DreamWalker подскажет, каким образом сделать данные замеры более объективными). Далее представлен результат измерений:
Сразу оговорюсь, что не могу быть твёрдо уверен в отсутствии оптимизаций компилятором итогового кода, однако, судя по IL коду, каждая из функций содержит проверяемую операцию в единственном числе.
Измерения проводились на нескольких машинах с разным кол-вом LoopCount, однако, скорость распаковки из раза в раз превосходила упаковку в 3-8 раз.
Что такое упаковка и распаковка(boxing/unboxing)?
Что это вообще такое упаковка и распаковка (boxing/unboxing) и зачем она нужна?
Был бы рад примерам.
3 ответа 3
их нельзя помещать в коллекции и прочее.
Для того, чтобы обойти это неудобство, для всех примитивных типов существуют соответствующие классы-оболочки, объекты которых могут хранить значения примитивных типов, но обладает всеми свойствами нормальных объектов:
В тех случаях, когда по контексту требуются объекты (присваивание, вызов метода с передачей параметров), а мы используем значения примитивных типов (переменные или выражения типа 2 * 3), всегда происходит автоупаковка.
В версиях ниже JDK 1.5 было не легко преобразовывать примитивные типы данных, такие как int, char, float, double в их классы оболочки Integer, Character, Float, Double. Начиная с версии JDK 5 эта функция, преобразования примитивных типов в эквивалентные объекты, реализована автоматически. Это свойство известно как Автоупаковка(Autoboxing). Обратный процесс соответственно – Распаковка(Unboxing) т.е. процесс преобразования объектов в соответствующие им примитивные типы.
Пример кода для автоупаковки и распаковки представлен ниже:
Когда используется автоупаковка и распаковка?
Автоупаковка применяется компилятором Java в следующих условиях:
Всё ещё ищете ответ? Посмотрите другие вопросы с метками java java-faq или задайте свой вопрос.
Связанные
Похожие
Подписаться на ленту
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
дизайн сайта / логотип © 2021 Stack Exchange Inc; материалы пользователей предоставляются на условиях лицензии cc by-sa. rev 2021.12.10.40971
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
Мы в своем проекте занимаемся разработкой сервера на C#. Этот сервер должен выдерживать очень высокие нагрузки, по этой причине мы стараемся написать код как можно оптимальней. C# редко ассоциируют с высокой производительностью, но если с умом подходить к разработке, то можно достичь очень даже неплохого уровня.
Одним из недешевых процессов с точки зрения производительности является boxing и unboxing. Напоминалку о том, что это такое, можно найти тут. Недавно я решил посмотреть весь IL код наших проектов и поискать инструкции box и unbox. Нашлось достаточно много участков, boxing’а в которых можно избежать легким движением руки. Все случаи, приводящие к ненужному boxing’у, очевидны, и допускаются по невнимательности в моменты концентрации на функциональности, а не на оптимизации. Я решил выписать наиболее часто встречающиеся случаи, чтобы не забывать о них, а затем автоматизировать их исправление. В данной статье и перечислены эти случаи.
Сразу сделаю ремарку: чаще всего проблемы производительности лежат на более высоком уровне, и прежде чем править весь лишний boxing, нужно привести код к такому состоянию, когда от этого будет толк. Всерьез задумываться о таких вещах как boxing имеет смысл, если вы действительно хотите выжать максимум из C#.
Да простит меня русский язык, но далее в статье я буду использовать неожиданное для него слово «боксинг», чтобы глаз не цеплялся лишний раз в попытке найти строчку кода.
1. Передача value type переменных в методы String.Format, String.Concat и т.п.
Первое место по количеству боксинга держат строковые операции. Благо, в нашем коде это встречалось в основном в форматировании сообщения для исключений. Основное правило для избежания боксинга — это вызывать ToString() у value type переменной перед использованием в методах String.Format или при сложении строк.
То же самое, но в коде. Вместо:
Как мы видим, появляется инструкция constrained вместо box. Здесь написано, что следующий вызов callvirt будет напрямую у переменной, при условии, что thisType это value type, и есть реализация метода. Если же реализации метода нет, то всё равно произойдет боксинг.
Неприятный момент заключается в том, что почти у всех стоит Resharper, который подсказывает, что вызов ToString() лишний.
И еще насчет строк, а точнее их сложения. Иногда встречал код вроде:
Есть ложное ощущение, что char без проблем сложится со строкой, но char — это value type, поэтому здесь тоже будет боксинг. В этом случае всё-таки лучше писать так:
2. Вызов методов на generic переменных
На самом деле здесь не всё так плохо, так как данный IL код будет прооптимизирован JIT’ом, но случай занятный.
Положительным моментом является также то, что для вызова методов на generic переменных используется уже знакомая нам инструкция constrained, а это позволяет вызывать методы на value типах без боксинга. Если же метод работает и с value типами и с reference типами, то, например, сравнение на null лучше писать так:
3. Вызовы методов перечислений
Перечисления в C# сильно печалят. Проблема в том, что любой вызов метода у перечисления вызывает боксинг:
Более того, даже метод GetHashCode() вызывает боксинг. Поэтому если вам вдруг нужен хэш код от перечисления, то сначала сделайте приведение к его underlying типу. А еще, если вы вдруг используете перечисление как ключ в Dictionary, то сделайте собственный IEqualityComparer, иначе при каждом вызове GetHashCode() будет боксинг.
4. Перечисления в generic методах
Как мы видим, с перечислениями и тут всё не слава богу: происходит боксинг при каждом вызове метода ToUInt64(). Но зато наглядно видно, что вызов интерфейсного метода у Int32 не вызывает никакого боксинга.
А под конец и отчасти как вывод хочется добавить, что value типы здорово помогают поднять производительность, но нужно внимательно следить за тем, как они используются, иначе в результате боксинга главное их преимущество будет нивелировано.
В следующей статье мне хотелось бы рассказать о местах, где неочевидным образом находятся глобальные точки синхронизации, и как их обходить. Stay tuned.
Boxing and Unboxing (C# Programming Guide)
Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type. When the common language runtime (CLR) boxes a value type, it wraps the value inside a System.Object instance and stores it on the managed heap. Unboxing extracts the value type from the object. Boxing is implicit; unboxing is explicit. The concept of boxing and unboxing underlies the C# unified view of the type system in which a value of any type can be treated as an object.
The object o can then be unboxed and assigned to integer variable i :
The following examples illustrate how boxing is used in C#.
Performance
In relation to simple assignments, boxing and unboxing are computationally expensive processes. When a value type is boxed, a new object must be allocated and constructed. To a lesser degree, the cast required for unboxing is also expensive computationally. For more information, see Performance.
Boxing
Boxing is used to store value types in the garbage-collected heap. Boxing is an implicit conversion of a value type to the type object or to any interface type implemented by this value type. Boxing a value type allocates an object instance on the heap and copies the value into the new object.
Consider the following declaration of a value-type variable:
The following statement implicitly applies the boxing operation on the variable i :
It is also possible to perform the boxing explicitly as in the following example, but explicit boxing is never required:
Example
Unboxing
Unboxing is an explicit conversion from the type object to a value type or from an interface type to a value type that implements the interface. An unboxing operation consists of:
Checking the object instance to make sure that it is a boxed value of the given value type.
Copying the value from the instance into the value-type variable.
The following statements demonstrate both boxing and unboxing operations:
The following figure demonstrates the result of the previous statements:
For the unboxing of value types to succeed at run time, the item being unboxed must be a reference to an object that was previously created by boxing an instance of that value type. Attempting to unbox null causes a NullReferenceException. Attempting to unbox a reference to an incompatible value type causes an InvalidCastException.
Example
This program outputs:
Specified cast is not valid. Error: Incorrect unboxing.
If you change the statement:
the conversion will be performed, and you will get the output:
C# language specification
For more information, see the C# Language Specification. The language specification is the definitive source for C# syntax and usage.
Boxing/Unboxing
Зачем производить boxing?
Зачем нужно значение ValueType размещать на куче? int a =2 ; object obj =a; Ну и зачем так.
Ошибка boxing-преобразования
Хочу проверить код через int SortedLinkedList list1 = new SortedLinkedList (); Ошибка.
Да, об этом читал, но так как только начал изучать, не зная всех тонкостей не знаю стоит ли хорошо изучить или быстренько пройти и переключится на что то другое.
А на счет Enum, тут упаковка/распаковка нужна или просто у меня в примере код похож только?
Решение
Переменная в программировании — это место хранения информации.
В C# есть два вида переменных: ссылочные (reference) и значимые (value).
Переменные значимых типов хранят непосредственно значение, а переменные ссылочных типов хранят ссылку на значение, которое лежит где-то в другом месте.
В качестве аналогии можно привести файл и ярлык на файл: в первом хранятся непосредственно данные (значимый тип), а в ярлыке хранится всего лишь указание места, где хранятся данные — это ссылочный тип.
По ходу написания кода может возникнуть ситуация, когда с переменной значимого типа приходится работать как с переменной ссылочного типа: например, если эту переменную нужно передать в метод, который принимает на вход ссылку, а не значение.
Просто передать переменную не получится, потому что принимающая сторона будет воспринимать ее значение как ссылку: например, если в переменной значимого типа хранится значение «12345», то метод, считающий что ему дали ссылку подумает, что «12345» — это место, в котором нужно искать собственно значение. Ничего хорошего из этого, сами понимаете, не выйдет.
В таких ситуациях и производится запаковка: создается дополнительный объект-обертка, в него помещается значение из переменной, потом создается дополнительная переменная ссылочного типа, которая хранит ссылку на эту обертку, и уже такая переменная передается в метод.
Сами видите — много лишних телодвижений, да плюс сборщик мусора, который следит за памятью, должен этот дополнительный объект потом удалить, когда он уже станет не нужен.
Ну а распаковка — это обратный процесс, когда из переменной ссылочного типа вы извлекаете местоположение непосредственно данных, создаете новую переменную значимого типа и копируете в нее эти данные.