swift algorithm nsmutablearray value-type swift-dictionary

¿Diccionario en Swift con matriz mutable ya que el valor funciona muy lentamente? ¿Cómo optimizar o construir adecuadamente?



algorithm nsmutablearray (2)

Estoy tratando de construir una estructura de datos en Swift que asigne un número entero a una matriz de objetos (un diccionario con int como clave y matriz como valor). Los objetos son extremadamente pequeños y simplemente envuelven un UIColor y un Int. Tengo dos implementaciones, una que usa una matriz Swift como tipo de valor del Diccionario, mientras que la otra usa un NSMutableArray como tipo de valor. Mi código objetivo-C funciona extremadamente rápido, pero mi código Swift se ejecuta muy lento. Idealmente, no me gustaría usar un NSMutableArray, y me gustaría mantenerlo como una matriz Swift. La razón de esto es que estoy escribiendo un algoritmo y el rendimiento es importante, he notado algunos gastos generales con objC_msgSend. ¿Alguien puede ayudarme a optimizar mi código Swift? ¿Estoy haciendo algo mal o es solo un subproducto de tratar rápidamente los arreglos como tipos de valor? Si es así, me gustaría entender por qué el tipo de valor funciona tan lento en este caso, cuáles son mis opciones y cómo puede este escenario escalar en el futuro. A continuación he publicado un fragmento de código y los puntos de referencia resultantes:

Código de matriz rápida:

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

Rendimiento rápido:

Time after mapping: 45.0881789259997

Código 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)")

Rendimiento de NSMutableArray:

Time after mapping: 0.367132211999888

El objeto colorInfo es un diccionario que asigna objetos UIColor a un valor entero que representa un recuento. El código esencialmente mapea esto, mapeando un número entero a una matriz UIColor (es una matriz porque múltiples colores pueden tener el mismo recuento). El colorInfo tiene 60,000 UIColor, pares de valores de clave Int dentro de él.


Copiar al escribir es algo complicado, y debe pensar detenidamente cuántas cosas comparten una estructura que está tratando de modificar. El culpable está aquí.

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

Esto genera un valor temporal que se modifica y se vuelve a colocar en el diccionario. Dado que dos "cosas" están mirando la misma estructura de datos subyacente (el diccionario y el append ), obliga a copiar y escribir.

El secreto para solucionar esto es asegurarse de que solo haya una copia cuando la modifique. ¿Cómo? Sácalo del diccionario. Reemplace esto:

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

que tiene un tiempo de ejecución de:

Elapsed Time: 74.2517465990022 53217

con este:

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

que tiene un tiempo de ejecución de:

Elapsed Time: 0.370953808000195 53217


Tuve un poco de trabajo hasta que apareció Swift 4.2

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

es rápido y legible