Почему пустые классы 8 байт и большие классы всегда> 8 байт?

class foo { }

writeln(foo.classinfo.init.length); // = 8 bytes

class foo { char d; }

writeln(foo.classinfo.init.length); // = 9 bytes

действительно ли d хранит что-либо в этих 8 байтах, и если да, то что? Это кажется огромной тратой, если я просто обертываю несколько типов значений, то класс значительно раздувает программу, особенно если я использую их много. Символ становится в 8 раз больше, а int-в 3 раза больше.

минимальный размер структуры - 1 байт.

4 ответов


В D объект имеет заголовок, содержащий 2 указателя (поэтому он может быть 8bytes или 16 в зависимости от вашей архитектуры).

первый указатель на таблицу виртуальных методов. Это массив, который генерируется компилятором, заполненным указателем функции, поэтому возможна виртуальная отправка. Все экземпляры одного класса используют одну и ту же таблицу виртуальных методов.

второй указатель-это монитор. Используется для синхронизации. Не уверен, что это поле останется здесь навсегда, потому что D акцент на локальное хранение и неизменность, которые делают синхронизацию на многих объектах бесполезной. Поскольку это поле старше этих функций, оно все еще здесь и может быть использовано. Однако в будущем она может исчезнуть.

такой заголовок на объекте очень распространен, вы найдете то же самое в Java или C#, например. Вы можете посмотреть ЗДЕСЬ Для больше информации:http://dlang.org/abi.html


D использует два машинных слова в каждом экземпляре класса для:

  1. указатель на таблицу виртуальных функций. Содержит адреса виртуальных методов. Первые точки входа в класс classinfo, который также используется динамическими слепками.

  2. монитор, который позволяет synchronized(obj) синтаксис, документально здесь.

эти поля описаны в документации здесь (прокрутите вниз до "свойства класса") и здесь (прокрутите вниз до "Classes").


Я не знаю подробностей D, но в Java и .net каждый объект класса содержит информацию о своем типе, а также содержит информацию о том, является ли он целью каких-либо блокировок монитора, имеет ли он право на окончательную очистку и различные другие вещи. Наличие стандартных средств, с помощью которых все объекты хранят такую информацию, может сделать многие вещи более удобными как для пользователей, так и для разработчиков языка и/или фреймворка. Кстати, в 32-разрядных версиях .net, накладные расходы для каждого объекта составляют 8 байт, за исключением минимального размера объекта в 12 байт. Этот минимум связан с тем, что, когда сборщик мусора перемещает объекты, ему необходимо временно хранить в старом месте ссылку на новую, а также какую-то связанную структуру данных, которая позволит ему исследовать произвольно глубокие вложенные ссылки без необходимости произвольно большого стека.

редактировать Если вы хотите использовать класс, потому что вы должны иметь возможность сохранять ссылки на элементы данных, пространство стоит дорого, и ваши шаблоны использования таковы, что вы будете знать, когда элементы данных по-прежнему полезны, а когда они устаревают, вы можете определить массив структур, а затем передать индексы элементам массива. Можно написать код, чтобы справиться с этим очень эффективно с практически нулевыми накладными расходами, при условии, что структура вашей программы позволяет гарантировать, что каждый выделенный элемент будет выпущен точно еще и вещи не используются как только они будут освобождены.

Если вы не сможете легко определить, когда последняя ссылка на объект выйдет из области видимости, восемь байтов будут очень разумным уровнем накладных расходов. Я ожидал бы, что большинство фреймворков заставят объекты выравниваться по 32-битным границам (поэтому я удивлен, что добавление байта приведет к увеличению размера до девяти, а не двенадцати). Если система будет иметь сборщик мусора, который работает лучше, чем Commodore 64 (*), он должен иметь абсолютный минимум немного накладных расходов на объект, чтобы указать, какие вещи используются, а какие нет. Кроме того, если вы не хотите иметь отдельные кучи для объектов, которые могут содержать дополнительную информацию, и те, которые не могут, каждый объект будет либо включать пространство для указателя дополнительной информации, либо включать пространство для всей дополнительной информации (блокировка, запросы уведомления об отказе и т. д.). Хотя это может быть полезно в в некоторых случаях, чтобы иметь отдельные кучи для двух категорий объектов, я сомневаюсь, что преимущества очень часто оправдывают дополнительную сложность.

(*) сборщик мусора Commodore 64 работал, выделяя строки сверху памяти вниз, в то время как переменные (которые не являются GC'Ed) были выделены снизу вверх. Когда память заполнялась, система сканировала все переменные, чтобы найти ссылку на строку, хранящуюся по самому высокому адресу. Затем эта строка будет перемещена в топ из памяти и все ссылки на нее будут обновлены. Затем система будет сканировать все переменные, чтобы найти ссылку на строку с самым высоким адресом ниже только что перемещенной и обновить все ссылки на нее. Процесс повторялся до тех пор, пока он не находил больше строк для перемещения. Этот алгоритм не требовал каких-либо дополнительных данных для хранения со строками в памяти, но он, конечно, был медленным. Сборщик мусора Commodore 128 хранится с каждой строкой в пространстве GC указатель на переменная, содержащая ссылку и Байт длины, который можно использовать для поиска следующей нижней строки в пространстве GC; таким образом, она может проверить каждую строку, чтобы узнать, используется ли она по-прежнему, переместив ее в верхнюю часть памяти, если это так. Гораздо быстрее, но за счет трех байтов накладных расходов на строку.


вы должны изучить требования к хранению для различных типов. Каждая инструкция, распределение хранилища (т. е. переменная/объект и т. д.) использует определенный объем пространства. В c# объект int32 типа integer должен хранить целочисленную информацию в размере 4 байт (32 бит). Он также может иметь другую информацию, потому что это объект, но ваш символьный тип данных, вероятно, требует только 1 байт информации. Если у вас есть конструкции, такие как for или while в вашем классе, эти вещи займут пространство тоже, потому что каждая из этих вещей говорит вашему классу что-то делать. Сам класс требует, чтобы в памяти было создано несколько инструкций, которые учитывали бы 8 начальных байтов.

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