Swift中的字典与Mutable Array作为值表现得非常慢?如何优化或构建正确?

AyB*_*Bay 7 algorithm value-type nsmutablearray swift swift-dictionary

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

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)")
Run Code Online (Sandbox Code Playgroud)

迅捷表现:

Time after mapping: 45.0881789259997
Run Code Online (Sandbox Code Playgroud)

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)")
Run Code Online (Sandbox Code Playgroud)

NSMutableArray表现:

Time after mapping: 0.367132211999888
Run Code Online (Sandbox Code Playgroud)

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

Rob*_*ier 15

写入时复制是一件棘手的事情,您需要仔细考虑共享一个您正在尝试修改的结构的内容.罪魁祸首就在这里.

countToColorMap[colorCount]?.append(CountedColor(color: color as! UIColor, colorCount: colorCount))
Run Code Online (Sandbox Code Playgroud)

这将生成一个临时值,该值将被修改并重新放入字典中.由于两个"事物"正在查看相同的底层数据结构(字典和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)]
}
Run Code Online (Sandbox Code Playgroud)

它的运行时间为:

Elapsed Time: 74.2517465990022
53217
Run Code Online (Sandbox Code Playgroud)

有了这个:

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

它的运行时间为:

Elapsed Time: 0.370953808000195
53217
Run Code Online (Sandbox Code Playgroud)

  • 在Swift 4.1(Xcode 9.3 beta版本)中,您现在可以通过`Dictionary`的`下标(_:default:)`进行变异而无需在字典中调用数组值的副本:)例如`countToColorMap [colorCount,default :[]].append(CountedColor(color:color as!UIColor,colorCount:colorCount))`.这是因为[下标现在使用地址](https://github.com/apple/swift/pull/12752),因此可以返回指向字典缓冲区中的值的指针. (6认同)
  • 这是关于如何实现Dictionary的许多其他选择的结果,结合优化器中的当前限制.随着时间的推移,您应该期望共同用途得到改善,但您可能总是必须了解复制写入在大型可变数据结构中是如何工作的(当性能很重要时,类似的事情在大多数语言中都是正确的;您通常必须了解它在下面是如何工作的).这种行为没有"类型理论"或"深层CS"原因.这就是Swift目前的工作方式. (2认同)