【问题标题】:Get the size (in bytes) of an object on the heap获取堆上对象的大小(以字节为单位)
【发布时间】:2017-12-18 04:12:48
【问题描述】:

我知道您可以使用 MemoryLayout<T>.size 来获取类型 T 的大小。

例如:MemoryLayout<Int32>.size // 4

但是,对于类实例(对象),MemoryLayout<T>.size 返回对对象的引用大小(在 64 位机器上为 8 个字节),而不是堆上实际对象的大小。

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 的互操作性。

【问题讨论】:

    标签: swift heap-memory dynamic-allocation memory-layout


    【解决方案1】:

    Apple 平台上的一个选项,因为 Swift 类是 currently built on top of Objective-C classes 那里,将使用 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)
    

    【讨论】:

    • 我不知道非 objc 类使用 ObjC 运行时。这不就是坦克性能吗? (通过发泄内联和去虚拟化?)
    • @Alexander Swift 在这方面实际上是两全其美,因为方法(包括属性访问器)不会自动暴露给 Obj-C(但 ivars 本身是,这就是为什么它有效的原因)。此外,即使对于暴露的成员(w/@objc),Swift 默认情况下也不会使用消息分派(爬过层次结构以查找实现)分派给他们,它使用带有自己的 vtables 的表分派(但是将返回给dynamic 成员的消息),并且在某些情况下(例如final/private 类),可以静态调度和内联。
    • @Alexander 我们获得了与 Obj-C 的快速简单的互操作性——我们可以将给定的类实例传递给 Obj-C,而无需装箱/复制它(考虑到有多少Apple 框架在 Obj-C 中)。如前所述,默认情况下,Swift 不会通过 Obj-C 运行时为非dynamic 成员使用消息分派,因此,例如,如果您调配一个方法,则从Swift won't call the swizzled implementation 调用它,除非它被标记为dynamic(强制消息发送)。
    • 至于资源,恐怕是相当有限的;我链接到的 Mike Ash 的博文很棒,但 Swift 存储库中也有一些文档,请参阅 github.com/apple/swift/blob/master/docs/ABI.rst#class-metadatagithub.com/apple/swift/blob/master/docs/…
    • @Alexander 在添加方法的情况下,无论如何你都必须通过 Obj-C 运行时才能调用它们(因为编译器对它们一无所知),所以你会得到消息发送。分派行为不是全有或全无(即所有类成员都通过 Obj-C 运行时分派,或者没有);它是在逐个成员的基础上完成的。
    【解决方案2】:

    就我在 Playground 中的测试而言,这个函数返回的值似乎很重要:

    func heapSize(_ obj: AnyObject) -> Int {
        return malloc_size(Unmanaged.passUnretained(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-compatible 的东西在堆中分配内存。 (在分配小块时,malloc 提供了一些填充以适应分配的大小到 2 的幂。因此,实例实际需要的大小可能小于 malloc_size。)

    我还没有测试过这会在多大程度上起作用,并且未记录的行为会在未来的任何时候发生变化,而不会收到任何通知

    但如果你真的知道这一点,这可能是一个很好的探索起点。

    【讨论】:

    • 好的起点!然而并不理想,因为malloc_size 可以返回比实际使用的值更大的值。
    猜你喜欢
    • 2011-03-26
    • 2014-01-27
    • 2021-12-15
    • 2016-04-08
    • 2013-02-01
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    相关资源
    最近更新 更多