Получить размер (в байтах) объекта в куче

Я знаю, что вы можете использовать MemoryLayout<T>.size для получения размера типа T.

Например: MemoryLayout<Int32>.size // 4

Однако для экземпляров класса (объектов) MemoryLayout<T>.size возвращает размер ссылки на объект (8 байт на 64-разрядных машинах), а не размер фактических объектов в куче.

class ClassA { // Objects should be at least 8 bytes
    let x: Int64 = 0
}

class ClassB  {// Objects should be at least 16 bytes
    let x: Int64 = 0
    let y: Int64 = 0
}

MemoryLayout<ClassA>.size // 8
MemoryLayout<ClassB>.size // 8, as well :(

Как я могу получить размер самих объектов?

Для тех, кто интересуется, мне это не нужно, я просто изучаю Swift и его взаимодействие с C.


person Alexander    schedule 28.10.2016    source источник


Ответы (2)


Один из вариантов на платформах Apple, поскольку классы Swift — это в настоящее время построен поверх классов Objective-C, будет использовать функцию времени выполнения Obj-C class_getInstanceSize, который дает вам размер экземпляра класса в байтах, включая любое заполнение.

// on a 64-bit machine (1 word == 8 bytes)...

import Foundation

class C {}
print(class_getInstanceSize(C.self)) // 16 bytes metadata for empty class 
                                     // (isa ptr + ref count)

class C1 {
    var i = 0
    var i1 = 0
    var b = false
}

print(class_getInstanceSize(C1.self)) // 40 bytes
// (16 metadata + 24 ivars, 8 for i + 8 for i1 + 1 for b + 7 padding)
person Hamish    schedule 13.07.2017
comment
Я не знал, что классы, отличные от objc, используют среду выполнения ObjC. Разве это не просто характеристики танка? (За счет инлайнинга и девиртуализации?) - person Alexander; 13.07.2017
comment
@Alexander Swift на самом деле получает лучшее из обоих миров в этом отношении, поскольку методы (включая средства доступа к свойствам) не отображаются автоматически для Obj-C (но сами ivars, поэтому это работает). Кроме того, даже для открытых членов (с @objc) Swift по умолчанию не отправляет им сообщения с помощью диспетчеризации сообщений (просматривая иерархию, чтобы найти реализацию), он использует диспетчеризацию таблиц со своими собственными виртуальными таблицами (но вернуться к сообщению для dynamic участников), а в некоторых случаях (например, для классов final/private) может статически отправляться и встраиваться. - person Hamish; 13.07.2017
comment
Интересно. В таком случае, какая польза вообще от использования среды выполнения ObjC? Например, если я динамически добавляю метод в такой класс во время выполнения, как он узнает, что нужно обратиться к среде выполнения, чтобы проверить его, если он не использует objc_msgsend? Знаете ли вы какие-либо ресурсы, документирующие это? - person Alexander; 13.07.2017
comment
@Alexander Александр Мы получаем быстрое и простое взаимодействие с Obj-C — мы можем просто передать данный экземпляр класса в Obj-C без необходимости упаковывать / копировать его (что очень полезно, учитывая, сколько фреймворков Apple находится в Obj-C) . Как уже говорилось, Swift по умолчанию не использует отправку сообщений через среду выполнения Obj-C для членов, не являющихся dynamic, поэтому, если, например, вы используете метод, вызывая его из Swift не будет вызывать swizzled реализацию, если она не помечена как dynamic (принудительная отправка сообщения). - person Hamish; 13.07.2017
comment
Что касается ресурсов, боюсь, они довольно ограничены; Сообщение в блоге Майка Эша, на которое я ссылаюсь, прекрасное, но в репозитории Swift также есть некоторая документация, см. github.com/apple/swift/blob/master/docs/ABI.rst#class-metadata & github.com/apple/swift/blob/master/docs/ - person Hamish; 13.07.2017
comment
@Hasish Я понимаю случай с существующими членами (должно быть dynamic для динамического изменения), но что, если я добавлю нового участника? Вы не можете применить dynamic к классу, поэтому должен ли существовать хотя бы один член dynamic, что потребует использования среды выполнения и возможности поиска новых добавленных селекторов? - person Alexander; 13.07.2017
comment
@Alexander В случае добавления методов вам все равно придется пройти через среду выполнения Obj-C, чтобы вызвать их (поскольку компилятор ничего о них не знает), поэтому вы получите отправку сообщений. Поведение отправки не все или ничего (т. е. все члены класса отправляются через среду выполнения Obj-C или ни один); это делается на индивидуальной основе. - person Hamish; 13.07.2017
comment
Ха, ты прав! Я не считал, что вам нужно будет пройти через среду выполнения, чтобы вызвать такой вызов. Спасибо за информацию! - person Alexander; 13.07.2017
comment
@Александр Не беспокойся! :) - person Hamish; 13.07.2017

Насколько я тестировал в Playground, эта функция возвращает вроде бы значимое значение:

func heapSize(_ obj: AnyObject) -> Int {
    return malloc_size(Unmanaged.passRetained(obj).toOpaque())
}

class MyClass {
    //no properites...
}
let myObj = MyClass()
print(heapSize(myObj)) //->16

class MyBiggerClass {
    var str: String?
    var i: Int = 0
}
let myBiggerObj = MyBiggerClass()
print(heapSize(myBiggerObj)) //->64

Кажется, что текущая среда выполнения Swift использует malloc-совместимое что-то для выделения памяти в куче. (malloc дает некоторое дополнение, чтобы соответствовать выделенному размеру в степени 2 при выделении небольших фрагментов. Таким образом, фактически необходимый размер для экземпляра может быть меньше, чем malloc_size.)

Я не проверял, насколько это будет работать, и недокументированное поведение, зависящее только от текущей реализации, может измениться в любое время в будущем без каких-либо уведомлений.

Но если вы действительно это знаете, это может стать хорошей отправной точкой для исследования.

person OOPer    schedule 30.10.2016
comment
Хорошая отправная точка! Однако это не идеально, так как malloc_size может возвращать большее значение, чем фактически используемое. - person Alexander; 31.10.2016