В релиз ноутах Go 1.15 я обнаружил следующую запись:
Чтобы понять, как данная оптимизация работает, нужно освежить в памяти устройство interface{} в Go. Я не буду сильно погружаться в эту тему, просто напомню основные идеи.
Внутри src/runtime/runtime2.go есть вот такая структура:
Это и есть наш интерфейс. По факту interface{} представляет собой всего 2 указателя:
Визуализируем полученные знания и пойдем дальше.
В Go аллокации новых объектов в хипе дорогие. Поэтому если вы хотите написать производительный код, то с этой проблемой обязательно столкнетесь. Так что любые, даже на первый взгляд незначительные, оптимизации могут улучшить производительность всего приложения.
Проблема, которую решает рассматриваемая оптимизация, заключается в том, что аллоцировать объекты под небольшие целые числа расточительная затея.
Вот что сделали ребята из Go. В пакете runtime у них уже был статический массив целых чисел от 0 до 255. В момент, когда происходит конвертация целого числа в interface{}, происходит проверка находится ли это число заданном диапазоне и если да, то в поле data типа iface записывается указатель на элемент в этом массиве. Тем самым происходит избавление от лишней аллокации.
Изменения можете посмотреть на гихабе.
В Go подобного рода оптимизации не новы. Так, если вы создаете односимвольную ascii строку, то никаких выделений памяти не будет. Не будет их все по тому же сценарию: runtime Go содержит в себе статический массив односимвольных строк. Кстати, не стоит волноваться, так как сейчас в runtime живет всего лишь один статический массив значений от 0 до 255. Он переиспользуется для строковых представлений.
Конвертация малых целочисленных значений в интерфейс теперь происходит без аллокаций.В этой небольшой заметке я расскажу в чем заключается оптимизация.
Как устроен interface{} в Go
Чтобы понять, как данная оптимизация работает, нужно освежить в памяти устройство interface{} в Go. Я не буду сильно погружаться в эту тему, просто напомню основные идеи.
Внутри src/runtime/runtime2.go есть вот такая структура:
type iface struct {
tab *itab
data unsafe.Pointer
}
Это и есть наш интерфейс. По факту interface{} представляет собой всего 2 указателя:
- data — указатель на сами данные, под которые была выделенная память на хипе
- tab — метаинформация об интерфейсе и базовом типе
Визуализируем полученные знания и пойдем дальше.
В чем собственно проблема
В Go аллокации новых объектов в хипе дорогие. Поэтому если вы хотите написать производительный код, то с этой проблемой обязательно столкнетесь. Так что любые, даже на первый взгляд незначительные, оптимизации могут улучшить производительность всего приложения.
Проблема, которую решает рассматриваемая оптимизация, заключается в том, что аллоцировать объекты под небольшие целые числа расточительная затея.
Как ее решили
Вот что сделали ребята из Go. В пакете runtime у них уже был статический массив целых чисел от 0 до 255. В момент, когда происходит конвертация целого числа в interface{}, происходит проверка находится ли это число заданном диапазоне и если да, то в поле data типа iface записывается указатель на элемент в этом массиве. Тем самым происходит избавление от лишней аллокации.
Изменения можете посмотреть на гихабе.
В Go подобного рода оптимизации не новы. Так, если вы создаете односимвольную ascii строку, то никаких выделений памяти не будет. Не будет их все по тому же сценарию: runtime Go содержит в себе статический массив односимвольных строк. Кстати, не стоит волноваться, так как сейчас в runtime живет всего лишь один статический массив значений от 0 до 255. Он переиспользуется для строковых представлений.
zuborg
Непонятно, что мешает значения, которые помещаются в 64 бита, писать напрямую в interface-структуру вместо data pointer
serge-phi
Усложнение кода и замедление операций преобразования.
Я бы динамической хеш-таблицей расширил диапазон [0..255] до [-1000..1000]. Хотя это и не очень тривиально в многопоточной среде.
zuborg
Все равно доступ к данным по указателю для разных типов происходит в разных ветках кода, так что код практически не изменяется — для коротких типов доступ к памяти по указателю заменится на непосредственную конвертацию значения указателя в нужный тип.
вычислить хеш-значение это минимум несколько десятков тактов даже с использованием AES-NI
QtRoS
Соглашусь с предложением… Более того, возникает ощущение жуткого дежавю, где-то уже читал про подобное. Может в C#/.NET?..
StJimmy
Возможно то, что не на всех архитектурах указатель имеет 64-битную размерность?
zuborg
Ок, уточню свое замечание: