swift - 如何以高性能的方式将可变数组用作字典值?
问题描述
有没有办法将可变数组作为 Swift 字典的值而不损失性能?当我四处搜索时,大多数方法似乎建议从字典中获取数组作为 var,添加到它,然后将其设置回字典(示例)。然而,这似乎是非常低效的。
我使用 NSMutableArrays 将这种方法与 Objective-C 和 Swift 进行了基准测试,并且在 1M 条目的情况下,它似乎慢了 180 倍,并且似乎表现出非线性增长,这表明编译器也无法充分优化这一点。
import Foundation
let startDate = Date()
var dict = [Int:[Int]]()
for i in 0..<1_000_000 {
let key = i % 7
var arr = dict[key] ?? [Int]()
arr.append(i)
dict[key] = arr
}
print("Time spent: \(Date().timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate)")
// $ swiftc MutableDictionary.swift -o MutableDictionarySwift -framework Foundation -O
// $ ./MutableDictionarySwift
// Time spent: 18.316431999206543
目标-C:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
NSDate *startDate = [NSDate date];
NSMutableDictionary *dict = [NSMutableDictionary new];
for (int i = 0; i < 1000000; ++i) {
NSNumber *key = @(i % 7);
NSMutableArray *arr = dict[key] ?: [NSMutableArray new];
[arr addObject:@(i)];
dict[key] = arr;
}
NSLog(@"Number of elements: %lu", dict.count);
NSLog(@"Time spent: %f", [NSDate date].timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate);
}
}
// $ clang MutableDictionary.m -o MutableDictionaryObjc -framework Foundation -O3
// $ ./MutableDictionaryObjc
// 2020-11-26 17:41:17.686 MutableDictionaryObjc[12012:775366] Number of elements: 7
// 2020-11-26 17:41:17.686 MutableDictionaryObjc[12012:775366] Time spent: 0.098124
Swift 与 NSMutableArray:
import Foundation
let startDate = Date()
var dict = [Int:NSMutableArray]()
for i in 0..<1_000_000 {
let key = i % 7
let arr = dict[key] ?? NSMutableArray()
arr.add(i)
dict[key] = arr
}
print("Time spent: \(Date().timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate)")
// $ swiftc MutableDictionary2.swift -o MutableDictionarySwift2 -framework Foundation -O
// $ ./MutableDictionarySwift2
// Time spent: 0.10528397560119629
解决方案
请注意,Swift 数组和NSMutableArray
引擎盖下都做了同样的事情:当它们没有更多空间用于新项目时,它们会分配一个新缓冲区。新的缓冲区分配逻辑在 Swift 和 Objective-C 中应该非常相似。
正如 Alexander 在他的评论中指出的那样,您的 Swift 数组代码的问题在于您创建了存储到字典中的数组的本地副本。一旦你改变了本地数组,写时复制机制就会触发数组缓冲区的副本,这会导致另一个堆分配。数组越大,复制缓冲区所需的时间就越多。
您看到的非线性增长是由于数组缓冲区的增加并不经常发生,因为数组实现对容量增加有很好的启发式,以避免每次添加新元素时分配。然而,由于写时复制导致的缓冲区重复发生在每次迭代中。
如果您对 Swift 数组进行适当的变异,您将获得更好的结果:
for i in 0..<1_000_000 {
let key = i % 7
dict[key, default: [Int]()].append(i)
}
通过上述更改,测量和仪器都显示出显着提高的性能,Swift 代码比 Objective-C 代码更快。
推荐阅读
- python - 尝试使用 stockstats 来计算 rsi,出现错误“TypeError:列表索引必须是整数或切片,而不是 str”
- android - Xamarin Forms 连接麦克风以从 Azure 检索翻译语音 api 的数据
- android - 具有 Viewmodel 的 Android 上的 Headless Activity
- android - 以编程方式获取辅助功能显示尺寸 Android
- r - 对因子水平感到困惑并与 dplyr 发生变异
- bazel - Bazel 方面如何从 java_library 目标获取源文件列表?
- java - 无法在 eclipse photon 上安装 windowbuilder 插件
- image - Flutter pdf生成太慢,图像太慢
- c++ - 使用 stod() 转换小数点后带有字母的字符串时也不例外
- cassandra - Cassandra 表中的分布式行级锁定或实现更高的一致性