7

我正在尝试在 Swift 中构建一个数据结构,将 Integer 映射到对象数组(以 int 为键,数组为值的字典)。这些对象非常小,它们只是包装了一个 UIColor 和一个 Int。我有两种实现,一种使用 Swift 数组作为 Dictionary 的值类型,而另一种使用 NSMutableArray 作为值类型。我的 Objective-C 代码执行速度非常快,但我的 Swift 代码运行速度非常慢。理想情况下,我不想使用 NSMutableArray,而是希望将其保留为 Swift 数组。这样做的原因是我正在编写一个算法和性能问题,我注意到 objC_msgSend 有一些开销。谁能帮我优化我的 Swift 代码?我做错了什么还是这只是将数组作为值类型快速处理的副产品?如果是,我想了解为什么值类型在这种情况下执行如此缓慢,我的选择是什么,以及这种情况如何才能扩展?下面我发布了一个代码片段和结果基准:

快速数组代码:

let numColors = colorCount(filter: filter, colorInfoCount: colorInfo.count)
var colorCountsArray: [Int] = [Int]()
var countToColorMap: [Int:[CountedColor]] = [Int:[CountedColor]](minimumCapacity: capacity)
var topColors = [CountedColor]()

var startTime = CACurrentMediaTime()
for (color, colorCount) in colorInfo {
    colorCountsArray.append(colorCount)
    if countToColorMap[colorCount] != nil {
        countToColorMap[colorCount]?.append(CountedColor(color: color, colorCount: colorCount))
    } else {
        countToColorMap[colorCount] = [CountedColor(color: color, colorCount: colorCount)]
    }
}
var endTime = CACurrentMediaTime()
print("Time after mapping: \(endTime - startTime)")

迅速的表现:

Time after mapping: 45.0881789259997

NSMutableArray 代码:

let numColors = colorCount(filter: filter, colorInfoCount: colorInfo.count)
var colorCountsArray: [Int] = [Int]()
var countToColorMap: [Int:NSMutableArray] = [Int:NSMutableArray](minimumCapacity: capacity)
var topColors = [CountedColor]()


var startTime = CACurrentMediaTime()
for (color, colorCount) in colorInfo {
    colorCountsArray.append(colorCount)
    if countToColorMap[colorCount] != nil {
        countToColorMap[colorCount]?.add(CountedColor(color: color, colorCount: colorCount))
    } else {
        countToColorMap[colorCount] = NSMutableArray(object: CountedColor(color: color, colorCount: colorCount))
    }
}
var endTime = CACurrentMediaTime()
print("Time after mapping: \(endTime - startTime)")

NSMutableArray 性能:

Time after mapping: 0.367132211999888

colorInfo 对象是将 UIColor 对象映射到表示计数的整数值的字典。代码本质上是反向映射,将整数映射到 UIColor 数组(它是一个数组,因为多个颜色可以具有相同的计数)。colorInfo 内部有 60,000 个 UIColor、Int 键值对。

4

2 回答 2

16

写时复制是一件棘手的事情,您需要仔细考虑有多少东西共享您尝试修改的结构。罪魁祸首在这里。

countToColorMap[colorCount]?.append(CountedColor(color: color as! UIColor, colorCount: colorCount))

这将生成一个临时值,该值被修改并放回字典中。由于两个“事物”正在查看相同的底层数据结构(字典和append),因此它强制执行写入时复制。

解决此问题的秘诀是确保在修改它时只有一个副本。如何?把它从字典里拿出来。替换这个:

if countToColorMap[colorCount] != nil {
    countToColorMap[colorCount]?.append(CountedColor(color: color as! UIColor, colorCount: colorCount))
} else {
    countToColorMap[colorCount] = [CountedColor(color: color as! UIColor, colorCount: colorCount)]
}

它的运行时间为:

Elapsed Time: 74.2517465990022
53217

有了这个:

var countForColor = countToColorMap.removeValue(forKey: colorCount) ?? []
countForColor.append(CountedColor(color: color as! UIColor, colorCount: colorCount))
countToColorMap[colorCount] = countForColor

它的运行时间为:

Elapsed Time: 0.370953808000195
53217
于 2016-12-15T01:20:32.873 回答
2

在 swift 4.2 出现之前,我有一些工作要做

var countToColorMap = [Int: [CountedColor]]()

for (color, colorCount) in colorInfo {
    countToColorMap[colorCount, default: [CountedColor]()].append(CountedColor(color: color as! UIColor, colorCount: colorCount))
}

它快速且可读

于 2018-12-03T06:16:51.030 回答