Swift 大型数据集的数据初始化缓慢且内存效率低下
Swift Data initialization slow and memory inefficient with large dataset
如果我尝试用相对较大的 MutableRandomAccessSlice<Data>
初始化 Swift Data
结构,程序启动时内存使用量会变大,并且需要很长时间才能完成。然而,在 Objective-C 中用 NSData
做同样的事情似乎没有同样的问题。
例如,使用以下代码:
let startData = Data(count: 100_000_000)
let finalData = Data(startData[0..<95_234_877])
如果我使用编译它:
xcrun swiftc -O -sdk `xcrun --show-sdk-path --sdk macosx` -o output main.swift
执行(在我的 MacBook Air 2011 上)需要很长时间才能完成(87 秒)并且内存使用量达到顶峰(见下文高达 625MB):
$ time ./output
./output 85.21s user 1.29s system 99% cpu 1:26.91 total
$ top -o MEM
PID COMMAND %CPU TIME #TH #WQ #PORT MEM PURG CMPRS PGRP PPID STATE
38156 output 99.0 01:25.57 1/1 0 10 625M+ 0B 992M+ 38156 36025 running
如果我分析每个步骤,创建 startData
大约需要 0.00015 秒,从 startData
创建切片需要 0.000007 秒,其余时间用于初始化 finalData
。
如果我在 Objective-C 中做同样的事情:
NSData *startData = [[NSMutableData alloc] initWithLength:100000000];
NSData *finalData = [startData subdataWithRange:NSMakeRange(0, 95234877)];
大约只需要 0.00017 秒。
我在 Swift 示例中做错了什么吗?两者之间似乎有很大的差异。
如您所见,Objective-C 代码 [startData subdataWithRange:NSMakeRange(0, 95234877)]
等同于 startData.subdata(in: 0..<95_234_877)
。
写Data(startData[0..<95_234_877])
时,Swift调用RangeReplaceableCollection
的public convenience init<S : Sequence where S.Iterator.Element == Iterator.Element>(_ elements: S)
,定义在RangeReplaceableCollection.swift.gyb中。实现的核心部分是这样的:
for element in newElements {
append(element)
}
你知道对一个集合重复 append
可能效率低下。
而且,如果你想从 [UInt8]
初始化一个 Data
,你最好调用一个特定于 [UInt8]
的初始化程序:
let data = Data(bytes: [UInt8](repeating: 0, count: 10_000_000))
Data([UInt8](repeating: 0, count: 100_000_000))
调用上述 RangeReplaceableCollection
中的初始化程序。
在我看来,Swift 应该更多地优化此类默认实现,但很难使它们像特定于类型的操作一样高效。
如果我尝试用相对较大的 MutableRandomAccessSlice<Data>
初始化 Swift Data
结构,程序启动时内存使用量会变大,并且需要很长时间才能完成。然而,在 Objective-C 中用 NSData
做同样的事情似乎没有同样的问题。
例如,使用以下代码:
let startData = Data(count: 100_000_000)
let finalData = Data(startData[0..<95_234_877])
如果我使用编译它:
xcrun swiftc -O -sdk `xcrun --show-sdk-path --sdk macosx` -o output main.swift
执行(在我的 MacBook Air 2011 上)需要很长时间才能完成(87 秒)并且内存使用量达到顶峰(见下文高达 625MB):
$ time ./output
./output 85.21s user 1.29s system 99% cpu 1:26.91 total
$ top -o MEM
PID COMMAND %CPU TIME #TH #WQ #PORT MEM PURG CMPRS PGRP PPID STATE
38156 output 99.0 01:25.57 1/1 0 10 625M+ 0B 992M+ 38156 36025 running
如果我分析每个步骤,创建 startData
大约需要 0.00015 秒,从 startData
创建切片需要 0.000007 秒,其余时间用于初始化 finalData
。
如果我在 Objective-C 中做同样的事情:
NSData *startData = [[NSMutableData alloc] initWithLength:100000000];
NSData *finalData = [startData subdataWithRange:NSMakeRange(0, 95234877)];
大约只需要 0.00017 秒。
我在 Swift 示例中做错了什么吗?两者之间似乎有很大的差异。
如您所见,Objective-C 代码 [startData subdataWithRange:NSMakeRange(0, 95234877)]
等同于 startData.subdata(in: 0..<95_234_877)
。
写Data(startData[0..<95_234_877])
时,Swift调用RangeReplaceableCollection
的public convenience init<S : Sequence where S.Iterator.Element == Iterator.Element>(_ elements: S)
,定义在RangeReplaceableCollection.swift.gyb中。实现的核心部分是这样的:
for element in newElements {
append(element)
}
你知道对一个集合重复 append
可能效率低下。
而且,如果你想从 [UInt8]
初始化一个 Data
,你最好调用一个特定于 [UInt8]
的初始化程序:
let data = Data(bytes: [UInt8](repeating: 0, count: 10_000_000))
Data([UInt8](repeating: 0, count: 100_000_000))
调用上述 RangeReplaceableCollection
中的初始化程序。
在我看来,Swift 应该更多地优化此类默认实现,但很难使它们像特定于类型的操作一样高效。