Сколько занимают памяти переменные
Я не очень знаком с указателями, так как я в основном использую Java, а у Java нет указателей, и сейчас я изучаю C ++. В учебнике по C ++, чтобы узнать размер памяти, занимаемой переменной, он использовал размер указателя на переменную, т.е.
Когда я запускаю приведенный выше код, я получаю выходные данные 4 и 8. Но мой друг скомпилировал и выполнил тот же код на своей машине, и у него были выходные данные 4 и 4. Я не знаю, почему это происходит и почему репетитор использовал sizeof для указателя на переменную вместо самой переменной, так как он хотел знать объем памяти, занимаемый этой переменной. Я знаю, что переменные в C / C ++ имеют различную емкость памяти из-за разной архитектуры, по крайней мере, так меня учили в C. То, что int в 64-битной машине имеет другой размер по сравнению с 32-битной. Но я думаю, что мои результаты должны быть как минимум согласованными, т.е. 8 и 8, или 4 и 4. Я использую 64-битную архитектуру и 64-битную ОС, мой друг использует 64-битную архитектуру с 32-битной ОС.
Решение
дает вам размер переменной p , который имеет тип int * ,
Это не такой же как
который даст вам размер, занимаемый int переменная.
Сказав, что, просто чтобы уточнить, чтобы узнать размер, занимаемый переменной, вам нужно использовать sizeof оператор этой переменной, а не указатель на эту переменную. ( Что вы узнали в первый год является правильный ).
Обратите внимание, что размер указателя зависит от архитектуры, поэтому он может варьироваться. В некоторых архитектурах размер указателя может быть 32 бита ( sizeof вернет 4), в некоторых других это может быть 64 бит ( sizeof вернется 8).
Другие решения
Вы должны использовать sizeof к самому значению, чтобы узнать, сколько памяти занято переменной. Я думаю, что репетитор сделал неправильно.
sizeof(ptr) дает размер указателя ptr и его размер определяется реализацией. То же самое верно для sizeof(int) , Вот почему вы и ваш друг получаете разные результаты.
Другие возможности, которые вы можете получить в виде 4 4 а также 8 8 ,
sizeof даст вам количество байтов, необходимое для представления объекта определенного типа. В вашей строке
std::cout << sizeof(n) << std::endl;
n является int так что это даст вам размер int valiable.
Линия std::cout << sizeof(ptr) << std::endl; ptr имеет указатель типа на целое число, так что это даст вам размер int* , Не гарантируется, что их значения будут одинаковыми, и они будут различаться в зависимости от архитектуры. Стандарт с ++ не говорит, какими должны быть размеры этих типов.
Из стандартной тяги N 3690:
Для компилятора все указатели имеют фиксированный размер независимо от типа данных.
Размер указателя в 32-битном компьютере составляет 4 байта, а в 64-битном размер указателя составляет 8 байтов. Вот почему ваш друг получает 4 в качестве размера указателя, а вы получаете 8.
Python - это фантастический язык программирования. Он также известен как довольно медленный, в основном из-за его огромной гибкости и динамических характеристик. Для многих приложений и областей это не проблема из-за их требований и различных методов оптимизации. Менее известно, что графы объектов Python (вложенные словари списков, кортежей и примитивных типов) занимают значительный объем памяти. Это может быть гораздо более серьезным ограничивающим фактором из-за его влияния на кеширование, виртуальную память, многопользовательскую работу с другими программами и в целом более быстрое исчерпание доступной памяти, которая является дефицитным и дорогим ресурсом.
Оказывается, нетривиально выяснить, сколько памяти фактически потребляется. В этой статье я расскажу о тонкостях управления памятью объекта Python и покажу, как точно измерить потребляемую память.
В этой статье я остановлюсь исключительно на CPython - основной реализации языка программирования Python. Эксперименты и выводы здесь не относятся к другим реализациям Python, таким как IronPython, Jython и PyPy.
Также я запустил числа на 64-битном Python 2.7. В Python 3 числа иногда немного отличаются (особенно для строк, которые всегда являются Unicode), но концепции одинаковы.
Практическое исследование использования памяти Python
Во-первых, давайте немного разберемся и получим конкретное представление о фактическом использовании памяти объектами Python.
Встроенная функция sys.getsizeof()
Модуль sys стандартной библиотеки предоставляет функцию getsizeof(). Эта функция принимает объект (и необязательный параметр по умолчанию), вызывает метод sizeof() объекта и возвращает результат, поэтому вы также можете сделать ваши объекты инспектируемыми.
Измерение памяти объектов Python
Давайте начнем с некоторых числовых типов:
Интересно. Целое число занимает 24 байта.
Хм . float также занимает 24 байта.
Вот это да. 80 байтов! Это действительно заставляет задуматься о том, хотите ли вы представлять большое количество вещественных чисел как числа с плавающей запятой или десятичные дроби.
Давайте перейдем к строкам и коллекциям:
Хорошо. Пустая строка занимает 37 байтов, и каждый дополнительный символ добавляет еще один байт. Это многое говорит о компромиссе между сохранением нескольких коротких строк, когда вы будете платить 37 байтов за каждую, а не одну длинную строку, где вы платите только один раз.
Строки Unicode ведут себя аналогично, за исключением того, что служебные данные составляют 50 байтов, и каждый дополнительный символ добавляет 2 байта. Это стоит учитывать, если вы используете библиотеки, которые возвращают строки Unicode, но ваш текст может быть представлен в виде простых строк.
Кстати, в Python 3 строки всегда имеют Unicode, а служебные данные составляют 49 байт (они где-то сохранили байт). Объект байтов имеет служебную информацию только 33 байта. Если у вас есть программа, которая обрабатывает много коротких строк в памяти, и вы заботитесь о производительности, рассмотрите Python 3.
В чем дело? Пустой список занимает 72 байта, но каждый дополнительный int добавляет всего 8 байтов, где размер int составляет 24 байта. Список, который содержит длинную строку, занимает всего 80 байтов.
История повторяется для кортежей. Накладные расходы пустого кортежа составляют 56 байтов против 72 списка. Опять же, эта разница в 16 байтов на последовательность - это низко висящий плод, если у вас есть структура данных с большим количеством небольших неизменяемых последовательностей.
Наборы и словари якобы вообще не растут при добавлении элементов, но отмечают огромные накладные расходы.
Суть в том, что у объектов Python огромные фиксированные накладные расходы. Если ваша структура данных состоит из большого количества объектов коллекций, таких как строки, списки и словари, которые содержат небольшое количество элементов каждый, вы много платите.
Функция deep_getsizeof()
Теперь, когда я напугал вас до полусмерти и продемонстрировал, что sys.getsizeof() может только сказать вам, сколько памяти занимает примитивный объект, давайте посмотрим на более адекватное решение. Функция deep_getsizeof() рекурсивно выполняет детализацию и вычисляет фактическое использование памяти графом объектов Python.
У этой функции есть несколько интересных аспектов. Она учитывает объекты, на которые ссылаются несколько раз, и учитывает их только один раз, отслеживая идентификаторы объектов. Другая интересная особенность реализации заключается в том, что она в полной мере использует абстрактные базовые классы модуля коллекций. Это позволяет функции очень лаконично обрабатывать любую коллекцию, которая реализует базовые классы Mapping или Container, вместо непосредственного обращения к множеству типов коллекций, таких как: строка, Unicode, байты, список, кортеж, dict, frozendict, OrderedDict, set, frozenset и т.д.
Давайте посмотрим на это в действии:
Строка длиной 7 занимает 44 байта (37 служебных данных + 7 байтов для каждого символа).
Пустой список занимает 72 байта (только накладные расходы).
python deep_getsizeof ([x], set ()) 124
Список, содержащий строку x, занимает 124 байта (72 + 8 + 44).
Список, содержащий строку x 5 раз, занимает 156 байтов (72 + 5 * 8 + 44).
Последний пример показывает, что deep_getsizeof() подсчитывает ссылки на один и тот же объект (строку x) только один раз, но подсчитывается указатель каждой ссылки.
Баг или фича
Оказывается, что у CPython есть несколько хитростей, поэтому числа, которые вы получаете от deep_getsizeof(), не полностью отражают использование памяти программой Python.
Подсчет ссылок
Python управляет памятью, используя семантику подсчета ссылок. Когда на объект больше не ссылаются, его память освобождается. Но пока есть ссылка, объект не будет освобожден. Такие вещи, как циклические ссылки, могут вас сильно укусить.
Маленькие объекты
Целые числа
CPython хранит глобальный список всех целых чисел в диапазоне [-5, 256]. Эта стратегия оптимизации имеет смысл, потому что маленькие целые числа всплывают повсюду, и, учитывая, что каждое целое число занимает 24 байта, оно экономит много памяти для типичной программы.
Это также означает, что CPython предварительно выделяет 266 * 24 = 6384 байта для всех этих целых чисел, даже если вы не используете большинство из них. Вы можете проверить это с помощью функции id(), которая дает указатель на фактический объект. Если вы называете id(x) несколько для любого x в диапазоне [-5, 256], вы будете каждый раз получать один и тот же результат (для одного и того же целого числа). Но если вы попробуете это для целых чисел за пределами этого диапазона, каждый из них будет отличаться (новый объект создается на лету каждый раз).
Вот несколько примеров в этом диапазоне:
Вот несколько примеров за пределами диапазона:
Память Python против системной памяти
Теперь эта память 100X может быть бесполезно захвачена в вашей программе, никогда больше не использоваться и лишать систему возможности выделять ее другим программам. Ирония заключается в том, что если вы используете модуль обработки для запуска нескольких экземпляров вашей программы, вы строго ограничите количество экземпляров, которые вы можете запустить на данном компьютере.
Профилировщик памяти
Чтобы измерить и измерить фактическое использование памяти вашей программой, вы можете использовать модуль memory_profiler. Я немного поиграл с этим, и я не уверен, что доверяю результатам. Он очень прост в использовании. Вы декорируете функцию (может быть главной (0 функция)) с помощью декоратора @profiler, и когда программа завершает работу, профилировщик памяти выводит на стандартный вывод удобный отчет, который показывает общее количество и изменения в памяти для каждой строки. Вот пример программы, которую я запускал под профилировщиком:
Как вы можете видеть, занято 22,9 МБ дополнительной памяти. Причина, по которой память не увеличивается при добавлении целых чисел как внутри, так и вне диапазона [-5, 256], а также при добавлении строки, заключается в том, что во всех случаях используется один объект. Непонятно, почему первый цикл диапазона (100000) в строке 8 добавляет 4,2 МБ, в то время как второй цикл в строке 10 добавляет всего 0,4 МБ, а третий цикл в строке 12 добавляет 0,8 МБ. Наконец, при удалении списков a, b и c освобождается -0.6MB для a и c, но для b добавляется 0.2MB. Я не могу понять эти странные результаты.
Заключение
CPython использует много памяти для своих объектов. Он использует различные приемы и оптимизации для управления памятью. Отслеживая использование памяти вашим объектом и зная модель управления памятью, вы можете значительно уменьшить объем памяти вашей программы.
Как вы узнали из урока «4.1 – Введение в основные типы данных», память на современных машинах обычно организована в блоки размером с байты, причем каждый байт памяти имеет уникальный адрес. До этого момента было полезно думать о памяти как о связке почтовых ящиков, куда мы можем помещать и извлекать информацию, а переменные в этой аналогии – имена для доступа к этим почтовым ящикам.
Однако эта аналогия не совсем верна в одном отношении – большинство объектов на самом деле занимают более 1 байта памяти. Один объект может использовать 2, 4, 8 или более последовательных адресов памяти. Объем памяти, который использует объект, зависит от его типа данных.
Тем не менее, есть несколько причин, по которым полезно знать, сколько памяти использует какой-либо объект.
Во-первых, чем больше памяти использует объект, тем больше информации он может вместить.
Один бит может содержать 2 возможных значения, 0 или 1:
2 бита могут содержать 4 возможных значения:
бит 0 | бит 1 |
---|---|
0 | 0 |
0 | 1 |
1 | 0 |
1 | 1 |
3 бита могут содержать 8 возможных значений:
бит 0 | бит 1 | бит 2 |
---|---|---|
0 | 0 | 0 |
0 | 0 | 1 |
0 | 1 | 0 |
0 | 1 | 1 |
1 | 0 | 0 |
1 | 0 | 1 |
1 | 1 | 0 |
1 | 1 | 1 |
В общем, объект из n битов (где n – целое число) может содержать 2 n (2 в степени n, также иногда записывается 2^n) уникальных значений. Следовательно, при байте из 8 битов, объект размером 1 байт может принимать 2 8 (256) различных значений. Объект, который использует 2 байта, может принимать 2^16 (65536) разных значений!
Таким образом, размер объекта ограничивает количество уникальных значений, которые он может принимать – объекты, которые используют больше байтов, могут принимать большее количество уникальных значений. Мы рассмотрим это дальше, когда поговорим подробнее о целых числах.
Ключевой момент
Начинающие программисты часто слишком много внимания уделяют оптимизации своего кода, чтобы использовать как можно меньше памяти. В большинстве случаев достигаемая разница незначительна. Сосредоточьтесь на написании поддерживаемого кода и оптимизируйте его только тогда и там, где выгода будет существенной.
Размеры основных типов данных
Следующий очевидный вопрос – «сколько памяти занимают переменные разных типов данных?». Вы можете быть удивлены, обнаружив, что размер конкретного типа данных зависит от компилятора и/или архитектуры компьютера!
C++ гарантирует только минимальный размер каждого базового типа данных:
Категория | Тип | Минимальный размер | Примечание |
---|---|---|---|
логический | bool | 1 байт | |
символ | char | 1 байт | всегда точно 1 байт |
wchar_t | 1 байт | ||
char16_t | 2 байта | тип C++11 | |
char32_t | 4 байта | тип C++11 | |
целочисленное значение | short | 2 байта | |
int | 2 байта | ||
long | 4 байта | ||
long long | 8 байт | тип C99/C++11 | |
с плавающей запятой | float | 4 байта | |
double | 8 байт | ||
long double | 8 байт |
Однако фактический размер переменных на вашем компьютере может быть другим (особенно int , который чаще всего составляет 4 байта).
Лучшая практика
Для максимальной совместимости не следует предполагать, что переменные могут быть больше указанного минимального размера.
Объекты базовых типов данных обычно работают очень быстро.
Оператор sizeof
Чтобы определить размеры типов данных на конкретной машине, C++ предоставляет оператор с именем sizeof . Оператор sizeof – это унарный оператор, который принимает тип или переменную и возвращает ее размер в байтах. Вы можете скомпилировать и запустить следующую программу, чтобы узнать размеры некоторых из ваших типов данных:
Вот результат работы этой программы, полученный автором, на машине x64 при использовании Visual Studio:
Ваши результаты могут отличаться, если вы используете другой тип машины или другой компилятор. Обратите внимание, что вы не можете использовать оператор sizeof для типа void , поскольку он не имеет размера (это приведет к ошибке компиляции).
Для продвинутых читателей
Если вам интересно, что такое " \t " в приведенной выше программе, это специальный символ, который вставляет табуляцию (в этом примере мы используем ее для выравнивания выходных столбцов). Мы рассмотрим " \t " и другие специальные символы в уроке «4.11 – Символы».
Вы также можете использовать оператор sizeof для имени переменной:
Производительность при использовании базовых типов данных
На современных машинах объекты базовых типов данных работают быстро, поэтому производительность при использовании этих типов обычно не должна быть проблемой.
В качестве отступления.
из (1) и (2), которые приобретут больше пространства памяти или равное пространство будет приобретено?
оба занимают одинаковый объем памяти. Имена переменных предназначены только для того, чтобы помочь вам, программисту, запомнить, для чего предназначена переменная, и помочь компилятору связать различные виды использования одной и той же переменной. За исключением отладочных символов, они не отображаются в скомпилированном коде.
в C++ и большинстве статически скомпилированных языков имена переменных могут занимать больше места в процессе компиляции, но во время выполнения имена будут отброшены и, таким образом, не занимают места вообще.
в интерпретируемых языках и скомпилированных языках, которые обеспечивают интроспекцию/отражение времени выполнения, имя может занимать больше места.
кроме того, реализация языка повлияет на то, сколько имен переменных пространства займет. Исполнитель, возможно, решил использовать буфер фиксированной длины для каждого имени переменной, в этом случае каждое имя занимает одинаковое пространство независимо от длины. Или они могут иметь динамически выделенное пространство на основе длины.
в большинстве интерпретируемых языков имя будет храниться в таблице где-то в памяти, занимая, таким образом, разное пространство.
имя переменной в C/C++ не повлияет на размер получаемого исполняемого кода. Когда вы объявляете такую переменную, компилятор резервы пространство памяти (в случае int на x86/x64, четыре байта) для хранения значения. Для доступа или изменения значения он будет использовать адрес, а не имя переменной (которое теряется в процессе компиляции).
Если мое понимание верно, они будут занимать тот же объем памяти. Я считаю (и готов к тому, чтобы быть сбитым в пламени), что в C++ имена являются символическими, чтобы помочь пользователю, и компилятор просто создаст блок памяти, достаточный для хранения типа, который вы объявляете, в этом случае int. Таким образом, они должны занимать одинаковый размер памяти, т. е. память, необходимую для хранения адреса.
как вы можете видеть, ни a , ни bcdefghijklmnopqrstuvwxyz отразить в выходных данных ассемблера. Таким образом, длина имени переменной не имеет значения во время выполнения с точки зрения памяти.
но имена переменных являются огромными вкладчиками в дизайн программы. Некоторые программисты даже полагаются на хорошие соглашения об именах вместо комментариев, чтобы объяснить дизайн своей программы.
код должен быть написан так, чтобы полностью описать функциональность программы для читателей и только случайно интерпретироваться компьютерами. Нам трудно запоминать короткие имена долгое время, и нам трудно смотреть на длинные имена снова и снова подряд. Кроме того, короткие имена несут более высокую вероятность столкновений (поскольку пространство поиска меньше), но легче "держаться" в течение коротких периодов чтение.
таким образом, наши соглашения об именовании вещей должны учитывать ограничения человеческого мозга. Длина имени переменной должна быть пропорциональна расстоянию между ее определением и ее использованием и обратно пропорциональна частоте ее использования.
в современных компиляторах имя переменной не влияет на объем пространства, необходимый для ее хранения в C++.
имена полей (имена переменных экземпляра) в Java используют память, но только один раз на поле. Это для того, чтобы рефлексия работала. То же самое касается других языков, основанных на JVM, и я думаю, для DotNet.
нет.. Оба будут занимать равное пространство..
компиляторы есть. Они оптимизируют код, чтобы использовать как можно меньше места и работать как можно быстрее, особенно современные.
Читайте также: