Golang сколько памяти занимает переменная
Of course I know that it's 24 bytes, but I'd like to know it programmatically..
Do you have any ideas how to do this ?
Черная магия
Знаете ли вы, что выведет эта программа?
Результат программы зависит от архитектуры, на которой она исполняется. На little endian, например, AMD64, программа выводит . На big endian — единицу. Результат разный, потому что на little endian эта единица оказывается в середине числа, а на big endian — в конце.
На свете все еще существуют процессоры, у которых endian переключается, например, Power PC. Выяснять, что за endian сконфигурирован на вашем компьютере, придется во время старта, прежде чем делать умозаключения, что делают такого рода unsafe-фокусы. Например, если вы напишите Go-код, который будет исполняться на каком-нибудь многопроцессорном сервере IBM.
Я привел этот код, чтобы объяснить, почему я считаю весь unsafe черной магией. Пользоваться им не надо. Но Кирилл считает, что надо. И вот почему.
Есть некая функция, которая делает то же самое, что и GOB — Go Binary Marshaller. Это Encoder, но на unsafe.
Фактически она берет кусок памяти и изображает из него массив байт.
Это даже не порядок — это два порядка. Поэтому Кирилл Даншин, когда пишет высокопроизводительный код, не стесняется залезть в кишки своей программы и устроить ей unsafe.
BenchmarkGob-4 | 200000 | 8466 нс/op | 120.94 МБ/с |
BenchmarkUnsafeMut-4 | 50000000 | 37 нс/op | 27691.06 МБ/с |
Больше специфических особенностей Go будем обсуждать 7 октября на GolangConf — конференции для тех, кто использует Go в профессиональной разработке, и тех, кто рассматривает этот язык в качестве альтернативы. Даниил Подольский как раз входит в Программный комитет, если хотите поспорить с этой статьей или раскрыть смежные вопросы — подавайте заявку на доклад.
Для всего остального, что касается высокой производительности, конечно, HighLoad++. Туда тоже принимаем заявки. Подпишитесь на рассылку и будете в курсе новостей всех наших конференций для веб-разработчиков.
Does anybody know how to get memory size of the variable ( int , string , []struct , etc) and print it? Is it possible?
Defer, как мы его любим
Языковую конструкцию defer многие знают и любят использовать. Довольно часто мы её используем так.
Но так его использовать нельзя! Каждый defer съедает 40 нс на операцию.
Я подумал, может это из-за inline? Может inline такой быстрый?
Direct инлайнится, а defer-функция инлайниться не может. Поэтому скомпилировал отдельную тестовую функцию без inline.
Ничего не изменилось, defer занял те же 40 нс. Defer дорогой, но не катастрофически.
Но там, где функция занимает больше микросекунды, уже все равно — можно воспользоваться defer.
Передача параметров
Как быстрее передавать параметры — по ссылке или по значению? Давайте проверим.
Я проверял следующим образом — сделал вложенные типы от 1 до 10.
В десятом вложенном типе будет 10 полей int64, и вложенных типов предыдущей вложенности тоже 10.
Дальше написал функции, которые создают тип вложенности.
Для тестирования использовал три варианта типа: маленький с вложенностью 2, средний с вложенностью 3, большой с вложенностью 5. Очень большой тест с вложенность 10 пришлось ставить на ночь, но там картина точно такая же как для 5.
В функциях передача по значению минимум вдвое быстрее, чем передача по ссылке. Связано это с тем, что передача по значению не нагружает escape-анализ. Соответственно, переменные, которые мы выделяем, оказываются на стеке. Это существенно дешевле для runtime, для garbage collector. Хотя он может и не успеть подключиться. Эти тесты шли несколько секунд — garbage collector, наверное, еще спал.
BenchmarkCreateSmallByValue-4 | 200000 | 8942 нс/оп |
BenchmarkCreateSmallByPointer-4 | 100000 | 15985 нс/оп |
BenchmarkCreateMediuMByValue-4 | 2000 | 862317 нс/оп |
BenchmarkCreateMediuMByPointer-4 | 2000 | 1228130 нс/оп |
BenchmarkCreateLargeByValue-4 | 30 | 47398456 нс/оп |
BenchmarkCreateLargeByPointer-4 | 20 | 61928751 нс/op |
Интерфейсы
Есть интерфейс и структура, которая его реализует.
Есть три варианта использовать метод increment. Напрямую от Struct: var testStruct = testTypeStruct<> .
От соответствующего конкретного интерфейса: var testInterface testTypeInterface = &testStruct .
С runtime конверсией интерфейса: var testInterfaceEmpty interface<> = &testStruct .
Ниже runtime конверсия интерфейса и использование напрямую.
Runtime конверсия интерфейса стоит, но не дорого — специально отказываться не надо. Но старайтесь обойтись без этого там, где возможно.
- Dereference — разыменование указателей — бесплатно.
- Анонимные функции — бесплатно.
- Интерфейсы — бесплатно.
- Runtime конверсия интерфейса — НЕ бесплатно.
6 Answers 6
NOTE: The OP is mistaken. The unsafe.Sizeof does return 24 on the example Coord3d struct. See comment below.
if not working with 64 bit ints then should be lodged as a bug. The use of unsafe.Sizeof appears in lots of places in the Go library code and is intended as a cannoical way to do this, especially when interfacing to C libraries and OS API calls. At any rate, it certainly works correctly in code I've written using it.
So I checked with the code shown below. The OP was mistaken. The unsafe.Sizeof does correctly return 24 for his example data structure. (My Go compiler was updated first week of January 2010.) package main import ( unsafe "unsafe" fmt "fmt" ) type Coord3d struct < X, Y, Z int64 >func main() < var pt Coord3d; const size = unsafe.Sizeof(pt); fmt.Printf("Size of Coord3d: %d\n",size) >
You're right, I was mistaken, I did some experiments with binary.TotalSize and mixed that up with my Sizeof experiments.. Thanks for the demo solution, I couldn't really conclude the right use of it, as I just started to use Go a few days ago, and now trying out how to do the 'usual stuff'.. Finding usefull examples isn't that easy yet with Go, although reading the library source turned out to be quite instructive (as with python, for example).
Roger already showed how to use SizeOf method from the unsafe package. Make sure you read this before relying on the value returned by the function:
The size does not include any memory possibly referenced by x. For instance, if x is a slice, Sizeof returns the size of the slice descriptor, not the size of the memory referenced by the slice.
In addition to this I wanted to explain how you can easily calculate the size of any struct using a couple of simple rules. And then how to verify your intuition using a helpful service.
The size depends on the types it consists of and the order of the fields in the struct (because different padding will be used). This means that two structs with the same fields can have different size.
For example this struct will have a size of 32
and a slight modification will have a size of 24 (a 25% difference just due to a more compact ordering of fields)
As you see from the pictures, in the second example we removed one of the paddings and moved a field to take advantage of the previous padding. An alignment can be 1, 2, 4, or 8. A padding is the space that was used to fill in the variable to fill the alignment (basically wasted space).
Knowing this rule and remembering that:
- bool, int8/uint8 take 1 byte
- int16, uint16 - 2 bytes
- int32, uint32, float32 - 4 bytes
- int64, uint64, float64, pointer - 8 bytes
- string - 16 bytes (2 alignments of 8 bytes)
- any slice takes 24 bytes (3 alignments of 8 bytes). So []bool , [][][]string are the same (do not forget to reread the citation I added in the beginning)
- array of length n takes n * type it takes of bytes.
Armed with the knowledge of padding, alignment and sizes in bytes, you can quickly figure out how to improve your struct (but still it makes sense to verify your intuition using the service).
Зачастую в памяти программы хранятся структуры данных, которые изменяют свой размер динамически, по ходу работы программы. Примером такой структуры может быть кэш данных или журнал работы программы или данные, получаемые от внешних систем. При этом может возникнуть ситуация, когда потребление памяти растёт, возможностей оборудования не хватает, а конкретный механизм утечки не ясен.
В таком случае возникает желание измерить потребление памяти объектами программы по запросу, чтобы, например, отобразить статистику системы или передать метрики в систему мониторинга. Однако средствами языка это в общем случае невозможно. В Go нет инструментов для определения размера переменных во время работы программы.
Поэтому я решил написать небольшой пакет, который предоставляет такую возможность. Основным инструментом является рефлексия (пакет «reflection»). Всех интересующихся вопросом такого профилирования приложения приглашаю к дальнейшему чтению.
Сначала нужно сказать пару слов по поводу встроенных функций
Эти функции эквивалентны и зачастую в Интернете именно их рекомендуют для определения размера переменных. Но эти функции возвращают не размер фактической переменной, а размер в байтах для контейнера переменной (грубо — размер указателя). К примеру, для переменной типа int64 эти функции вернут корректный результат, поскольку переменная данного типа содержит фактическое значение, а не ссылку на него. Но для типов данных, содержащих в себе указатель на фактическое значение, вроде слайса или строки, эти функции вернут одинаковое для всех переменных данного типа значение. Это значение соответствует размеру контейнера, содержащего ссылку на данные переменной. Проиллюстрирую примером:
В результате получим:
Как видите, фактический размер переменной не вычисляется.
В стандартной библиотеке есть функция binary.Size() которая возвращает размер переменной в байтах, но только для типов фиксированного размера. То есть если в полях вашей структуры встретится строка, слайс, ассоциативный массив или просто int, то функция не применима. Однако именно эту функция я взял за основу пакета size, в котором попытался расширить возможности приведённого выше механизма на типы данных без фиксированного размера.
Для определения размера объекта во время работы программы необходимо понять его тип, вместе с типами всех вложенных объектов, если это структура. Итоговая структура, которую необходимо анализировать, в общем случае представляется в виде дерева. Поэтому для определения размера сложных типов данных нужно использовать рекурсию.
Таким образом вычисление объёма потребляемой памяти для произвольного объекта представляется следующим образом:
- алгоритм определение размера переменной простого (не составного) типа;
- рекурсивный вызов алгоритма для элементов массивов, полей структур, ключей и значений ассоциативных массивов;
- определение бесконечных циклов;
Для анализа типа и значения переменной пакет «reflection» упаковывает переменную в пустой интерфейс (interface<>). В Go пустой интерфейс может содержать любой объект. Кроме того, интерфейс в Go представлен контейнером, содержащим два поля: тип фактического значения и ссылку на фактическое значение.
Именно отображение анализируемого значения в пустой интерфейс и обратно послужило основанием для названия самого приёма — reflection.
Для лучшего понимания работы рефлексии в Go рекомендую статью Роба Пайка в официальном блоге Go. Перевод этой статьи был на Хабре.
В конечном итоге был разработан пакет size, который можно использовать в своих программах следующим образом:
Язык Go набирает популярность. Настолько уверенно, что появляется все больше конференций, например, GolangConf, а язык входит в десятку самых высокооплачиваемых технологий. Поэтому уже имеет смысл разговаривать о его специфических проблемах, например, производительности. Кроме общих для всех компилируемых языков проблем, у Go есть и свои собственные. Они связаны с оптимизатором, стеком, системой типов и моделью многозадачности. Способы их решения и обхода иногда бывают весьма специфическими.
Даниил Подольский, хоть и евангелист Go, тоже встречает в нем много странного. Все странное и, главное, интересное собирает и тестирует, а потом рассказывает об этом на HighLoad++. В расшифровке доклада будут цифры, графики, примеры кода, результаты работы профайлера, сравнение производительности одних и тех же алгоритмов на разных языках — и все остальное, за что мы так ненавидим слово «оптимизация». В  расшифровке не будет откровений — откуда они в таком простом языке, — и всего, о чем можно прочесть в газетах.
Доклад совместно готовили Даниил Подольский и Кирилл Даншин, но с докладом выступал Даниил, а Кирилл помогал ментально.
5 Answers 5
You can use the unsafe.Sizeof function for this. It returns the size in bytes, occupied by the value you pass into it. Here's a working example:
Take note that some platforms explicitly disallow the use of unsafe, because it is.. well, unsafe. This used to include AppEngine. Not sure if that is still the case today, but I imagine so.
As @Timur Fayzrakhmanov notes, reflect.TypeOf(variable).Size() will give you the same information. For the reflect package, the same restriction goes as for the unsafe package. I.e.: some platforms may not allow its use.
What if I have x := make([][256]byte, 8, 16) and I want to know the byte size of x ? unsafe.Sizeof(x) will just return 12, no matter how many bytes are allocated.
I think this is incorrect, unsafe.Sizeof returns the size of the header of slices and strings, which is just a pointer and a length (plus cap for slices). That will be a constant size. The question is about the memory usage of the value.
The size of a variable can be determined by using unsafe.Sizeof(a) . The result will remain the same for a given type (i.e. int , int64 , string , struct etc), irrespective of the value it holds. However, for type string , you may be interested in the size of the string that the variable references, and this is determined by using len(a) function on a given string. The following snippet illustrates that size of a variable of type string is always 8 but the length of a string that a variable references can vary:
The last part of your question is about assigning the length (i.e. an int value) to a string . This can be done by s := strconv.Itoa(i) where i is an int variable and the string returned by the function is assigned to s .
Note: the name of the converter function is Itoa , possibly a short form for Integer to ASCII. Most Golang programmers are likely to misread the function name as Iota .
Мне интересно узнать стоимость памяти map и slice , поэтому я написал программу для сравнения размеров. Я получаю объем памяти unsafe.Sizeof(s) , но, очевидно, это неправильно, потому что, когда я изменяю размер, вывод такой же.
Языковые конструкции
У нас есть эталон производительности — direct . Это функция, которая инкрементирует переменную и больше не делает ничего.
Результат функции — 1,46 нс на операцию. Это минимальный вариант. Быстрее 1,5 нс на операцию, наверное, не получится.
Switch, map и slice
Каждый новичок в Go спрашивает, что будет, если заменить switch на map. Будет быстрее?
Switch бывают разного размера. Я тестировал на трех размерах: маленький на 10 кейсов, средний на 100 и большой на 1000 кейсов. Switch на 1000 кейсов встречаются в реальном продакшн-коде. Конечно, никто руками их не пишет. Это автосгенерированный код, обычно type switch. Протестировал на двух типах: int и string. Показалось, что так получится нагляднее.
Маленький switch.Самый быстрый вариант — собственно switch. Вслед за ним сразу идет slice, где по соответствующему целочисленному индексу лежит ссылка на функцию. Map не в лидерах ни на int, ни на string.
BenchmarkSwitchIntSmall-4 | 500000000 | 3.26 нс/оп |
BenchmarkMapIntSmall-4 | 100000000 | 11.70 нс/оп |
BenchmarkSliceIntSmall-4 | 500000000 | 3.85 нс/оп |
BenchmarkSwitchStringSmall-4 | 100000000 | 12.70 нс/оп |
BenchmarkMapStringSmall-4 | 100000000 | 15.60 нс/оп |
Switch на строках существенно медленнее, чем на int. Если есть возможность сделать switch не на string, а на int, так и поступите.
Средний switch. На int все еще правит собственно switch, но slice его немного обогнал. Map по-прежнему плох. Но на string-ключе map быстрее, чем switch — ожидаемо.
BenchmarkSwitchIntMedium-4 | 300000000 | 4.55 нс/оп |
BenchmarkMapIntMedium-4 | 100000000 | 17.10 нс/оп |
BenchmarkSliceIntMedium-4 | 300000000 | 3.76 нс/оп |
BenchmarkSwitchStringMedium-4 | 50000000 | 28.50 нс/оп |
BenchmarkMapStringMedium-4 | 100000000 | 20.30 нс/оп |
Большой switch. На тысяче кейсов видно безоговорочную победу map в номинации «switch по string». Теоретически победил slice, но практически я советую здесь использовать все тот же switch. Map все еще медленный, даже учитывая, что у map для целочисленных ключей есть специальная функция хэширования. Вообще эта функция ничего и не делает. В качестве хэша для int выступает сам этот int.
BenchmarkSwitchIntLarge-4 | 100000000 | 13.6 нс/оп |
BenchmarkMapIntLarge-4 | 50000000 | 34.3 нс/оп |
BenchmarkSliceIntLarge-4 | 100000000 | 12.8 нс/оп |
BenchmarkSwitchStringLarge-4 | 20000000 | 100.0 нс/оп |
BenchmarkMapStringLarge-4 | 30000000 | 37.4 нс/оп |
Выводы. Map лучше только на больших количествах и не на целочисленном условии. Я уверен, что на любом из условий, кроме int, он будет вести себя также, как на string. Slice рулит всегда, когда условия целочисленные. Используйте его, если хотите «ускорить» свою программу на 2 нс.
Передача параметра по ссылке
Рассмотрим популярный миф.
Ничего не изменилось — ничего не стоит.
За исключением 3 нс на defer, но это спишем на флуктуации.
3 ответа
unsafe.SizeOf() и reflect.Type.Size() возвращают только размер переданного значения без рекурсивный обход структуры данных и добавление размеров указанных значений.
Срез является относительно простой структурой: reflect.SliceHeader , и, поскольку мы знаем, что он ссылается резервный массив, мы можем легко вычислить его размер "вручную", например:
Карты - это гораздо более сложные структуры данных, я не буду вдаваться в подробности, но посмотрите на этот вопрос + ответ: Golang: вычисление объема памяти (или длины в байтах) карты
Расчет размера любой переменной или структуры (рекурсивно)
Если вам нужны «реальные» числа, вы можете воспользоваться инструментом тестирования Go, который также может выполнять тестирование памяти. Передайте аргумент -benchmem , и внутри тестовой функции выделите только чью память вы хотите измерить:
(Удалите время и распечатки вызовов из getSlice() и getMap() , конечно.)
B/op значения говорят вам, сколько байтов было выделено для операции. allocs/op сообщает, сколько (отдельных) выделений памяти произошло за операцию.
В моей 64-битной архитектуре (где размер int равен 8 байтам) он говорит, что размер фрагмента, содержащего 2000 элементов, составляет примерно 16 КБ (в соответствии с 2000 * 8 байтов). Карта с 1000 int-int парами требуется приблизительно для выделения 42 КБ.
Это приводит к некоторым издержкам маршалинга, но я обнаружил, что это самый простой способ во время выполнения получить размер значения в go. Для моих нужд накладные расходы не были большой проблемой, поэтому я пошел по этому пути.
Это правильный путь, используя unsafe.Sizeof(s) . Просто результат останется неизменным для данного типа - целое число, строка и т. Д., Независимо от точного значения.
Sizeof принимает выражение x любого типа и возвращает размер в байтах гипотетической переменной v, как если бы v было объявлено через var v = x. Размер не включает в себя память, на которую может ссылаться х. Например, если x является слайсом, Sizeof возвращает размер дескриптора слайса, а не размер памяти, на которую ссылается слайс.
Вы можете использовать сортировку и затем сравнивать представления значений в байтах с Size() . Это только вопрос преобразования данных в байтовую строку.
Анонимные функции
Иногда новички спрашивают: «Анонимная функция — это дорого?»
Межгорутинное взаимодействие
Тема сложная, тестов я провел много и представлю самые показательные. Мы знаем следующие средства межгорутинного взаимодействия.
- Atomic. Это средства ограниченной применимости — можно заменить указатель или использовать int.
- Mutex используем широко со времен Java.
- Channel уникальны для GO.
- Buffered Channel — буферизованные каналы.
Профиль нагрузки бывает разным. Иногда все горутины хотят писать в одну переменную, но это редкость. Обычно все-таки какие-то пишут, какие-то читают. Из в основном читающих — 90% читают, из пишущих — 90% пишут.
Это код, который используется, чтобы горутина, которая обслуживает канал, могла обеспечить одновременно и чтение из переменной, и запись в нее.
BenchmarkMutex-4 | 100000000 | 16.30 нс/оп |
BenchmarkAtomic-4 | 200000000 | 6.72 нс/оп |
BenchmarkChan-4 | 5000000 | 239.00 нс/oп |
Это данные по одной горутине. Канальный тест выполняется на двух горутинах: одна обрабатывает Channel, другая в этот Channel пишет. А эти варианты были протестированы на одной.
- Direct пишет в переменную.
- Mutex берет лог, пишет в переменную и отпускает лог.
- Atomic пишет в переменную через Atomic. Он не бесплатный, но все-таки существенно дешевле Mutex на одной гарутине.
BenchmarkMutexFew-4 | 30000 | 55894 нс/оп |
BenchmarkAtomicFew-4 | 100000 | 14585 нс/оп |
BenchmarkChanFew-4 | 5000 | 323859 нс/оп |
BenchmarkChanBufferedFew-4 | 5000 | 341321 нс/оп |
BenchmarkChanBufferedFullFew-4 | 20000 | 70052 нс/оп |
BenchmarkMutexMostlyReadFew-4 | 30000 | 56402 нс/оп |
BenchmarkAtomicMostlyReadFew-4 | 1000000 | 2094 нс/оп |
BenchmarkChanMostlyReadFew-4 | 3000 | 442689 нс/оп |
BenchmarkChanBufferedMostlyReadFew-4 | 3000 | 449666 нс/оп |
BenchmarkChanBufferedFullMostlyReadFew-4 | 5000 | 442708 нс/оп |
BenchmarkMutexMostlyWriteFew-4 | 20000 | 79708 нс/оп |
BenchmarkAtomicMostlyWriteFew-4 | 100000 | 13358 нс/оп |
BenchmarkChanMostlyWriteFew-4 | 3000 | 449556 нс/оп |
BenchmarkChanBufferedMostlyWriteFew-4 | 3000 | 445423 нс/оп |
BenchmarkChanBufferedFullMostlyWriteFew-4 | 3000 | 414626 нс/оп |
Следующий — Mutex. Я ожидал, что Channel будет примерно таким же быстрым, как Mutex, но нет.
Причем Channel и буферизованный Channel выходят примерно в одну цену. А есть Channel, у которого буфер никогда не переполняется. Он на порядок дешевле, чем тот, у которого буфер переполняется. Только если буфер в Channel не переполняется, то стоит примерно столько же в порядках величин, сколько Mutex. Это то, чего я ожидал от теста.
Эта картина с распределением того, что сколько стоит, повторяется на любом профиле нагрузки — и на MostlyRead, и на MostlyWrite. Причем полный MostlyRead Channel стоит столько же, сколько и не полный. И MostlyWrite буферизованный Channel, в котором буфер не переполняется, стоит столько же, сколько и остальные. Почему это так, сказать не могу — еще не изучил этот вопрос.
Читайте также:
- Компьютерная томография является методом выбора при следующих новообразованиях
- Указано неверное имя файла или указанный файл не содержит сертификатов
- Неправильный заголовок файла img не найдено dskimg garmin
- Можно ли установить пиксель ретаргетинга вконтакте на сайт через google tag manager
- Oracle перенести индекс в другое табличное пространство