【问题标题】:Swift Dictionary Memory Consumption is AstronomicalSwift 字典的内存消耗是天文数字
【发布时间】:2015-12-04 03:32:43
【问题描述】:

谁能帮助解释一下为什么下面的代码在运行时会消耗超过 100 MB 的 RAM?

public struct Trie<Element : Hashable> {
    private var children: [Element:Trie<Element>]
    private var endHere : Bool

    public init() {
        children = [:]
        endHere  = false
    }
    public init<S : SequenceType where S.Generator.Element == Element>(_ seq: S) {
        self.init(gen: seq.generate())
    }
    private init<G : GeneratorType where G.Element == Element>(var gen: G) {
        if let head = gen.next() {
            (children, endHere) = ([head:Trie(gen:gen)], false)
        } else {
            (children, endHere) = ([:], true)
        }
    }
    private mutating func insert<G : GeneratorType where G.Element == Element>(var gen: G) {
        if let head = gen.next() {
            let _ = children[head]?.insert(gen) ?? { children[head] = Trie(gen: gen) }()
        } else {
            endHere = true
        }
    }
    public mutating func insert<S : SequenceType where S.Generator.Element == Element>(seq: S) {
        insert(seq.generate())
    }
}

var trie = Trie<UInt32>()
for i in 0..<300000 {
    trie.insert([UInt32(i), UInt32(i+1), UInt32(i+2)])
}

根据我的计算,上述数据结构的总内存消耗应该在以下某处...

3 * count * sizeof(Trie<UInt32>)

或者——

3 * 300,000 * 9 = 8,100,000 bytes = ~8 MB

这个数据结构在运行时消耗超过 100 MB 的原因是什么?

【问题讨论】:

    标签: xcode swift dictionary memory trie


    【解决方案1】:

    sizeof 仅报告堆栈上的静态足迹,Dictionary 只是对其内部引用类型实现的引用的一种包装,也是写时复制支持。换句话说,你的字典的键值对和哈希表是在堆上分配的,sizeof 没有覆盖。这适用于所有其他 Swift 集合类型。

    在您的情况下,您正在创建三个Trie - 间接地创建三个字典 - 300000 的每次迭代。如果@Macmade 提到的 96 字节分配是字典的最小开销,我不会感到惊讶(例如它的哈希桶)。

    增加存储空间也可能会产生成本。因此,您可以尝试查看在字典上设置 minimumCapacity 是否会有所帮助。另一方面,如果您不需要每次迭代生成的发散路径,您可以考虑使用间接枚举作为替代方案,例如

    public enum Trie<Element> {
        indirect case Next(Element, Trie<Element>)
        case End
    }
    

    应该使用更少的内存。

    【讨论】:

    • 安德斯,你是对的。我一直在玩 minimumCapacity ,它似乎对 0-2 范围内的值没有影响。该文档还指出“实际容量将是 2 的最小幂,即 >= minimumCapacity”。
    【解决方案2】:

    你的结构的大小是 9 字节,而不是 5。

    您可以通过sizeof查看:

    let size = sizeof( Trie< UInt32 > );
    

    此外,您迭代 300'000 次,但插入 3 个值(当然,这是一个 trie)。所以这是 900,000。
    无论如何,这本身并不能解释您正在观察的内存消耗。

    我的 Swift 不是很流利,我也不懂你的代码。
    也许它也有一些错误,使它分配的内存比需要的多。

    但无论如何,为了了解发生了什么,您需要在 Instruments (command-i) 中运行您的代码。

    在我的机器上,我可以看到 swift_slowAlloc 分配了 900'000 个96 字节
    这更像...

    假设您的代码没有错误,为什么要使用 96 个字节?
    嗯,这可能是因为为您的元素分配内存的方式。
    当满足请求时,内存分配器可能会分配比请求更多的内存。那可能是因为它需要一些内部元数据,因为分页,因为对齐,......

    但即便如此,这似乎真的很夸张,所以请使用工具并仔细检查您的代码在做什么。

    【讨论】:

    • Macmade,感谢您的回复。你是对的,结构大小是 9 个字节。意识到这一点后,我更新了我的问题。您还纠正了这仍然不能解释这里消耗的可笑内存量。我也使用了工具,并注意到总共 82.4 MB 的 96 字节分配。但为什么?需要更多调查...
    • 不客气。由于 swift_slowalloc 也可能是错误。你检查过这个雷达吗?看起来像你的例子:openradar.me/21375421
    • 只是简单地阅读雷达 - 我需要进一步调查并尝试使用更简化的示例进行重现以确认。
    • 这个错误似乎已在 Xcode 7 beta 5 中修复,因此我们可以将其排除为可能的罪魁祸首。
    猜你喜欢
    • 2016-08-25
    • 2014-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-14
    • 2015-07-04
    • 2016-06-20
    • 2014-09-19
    相关资源
    最近更新 更多