Dictionary in Swift with Mutable Array as value is performing very slow? How to optimize or construct properly?

那年仲夏 提交于 2019-11-26 12:17:28

问题


I am trying to build a data structure in Swift that maps an Integer to an array of objects (a dictionary with int as key and array as value). The objects are extremely small, and they simply wrap a UIColor and an Int. I have two implementations one that uses a Swift array as the Dictionary\'s value type, while the other uses a NSMutableArray as the value type. My objective-C code performs extremely fast, but my Swift code is running egregiously slow. Ideally, I would not like to use an NSMutableArray, and would like to keep it as a Swift array. Reason for this is I am writing an algorithm and performance matters, I have noticed some overhead with objC_msgSend. Can anyone help me optimize my Swift code? Am I doing something wrong or is this just a byproduct of swift treating array\'s as value types? If it is, I would like to understand why the value type performs so slow in this case, what my options are, and how can this scenario can scale going forward? Below I have posted a code snippet and the resulting benchmarks:

Swift Array Code:

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)\")

Swift Performance:

Time after mapping: 45.0881789259997

NSMutableArray code:

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 Performance:

Time after mapping: 0.367132211999888

The colorInfo object is a dictionary mapping UIColor objects to an Integer value representing a count. The code essentially reverse maps this, mapping an integer to a UIColor array (its an array because multiple Colors can have the same count). The colorInfo has 60,000 UIColor, Int key value pairs inside of it.


回答1:


Copy on write is a tricky thing, and you need to think carefully about how many things are sharing a structure that you're trying to modify. The culprit is here.

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

This is generating a temporary value that is modified and put back into the dictionary. Since two "things" are looking at the same underlying data structure (the dictionary, and append), it forces a copy-on-write.

The secret to fixing this is to make sure that there's only one copy when you modify it. How? Take it out of the dictionary. Replace this:

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

which has a runtime of:

Elapsed Time: 74.2517465990022
53217

with this:

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

which has a runtime of:

Elapsed Time: 0.370953808000195
53217



回答2:


I had some work arounds until swift 4.2 came along

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

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

it is fast and readable



来源:https://stackoverflow.com/questions/41079687/dictionary-in-swift-with-mutable-array-as-value-is-performing-very-slow-how-to

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!