Saltar al contenido

Kotlin: Actualizar elemento de lista inmutable

Nuestro team de redactores ha pasado mucho tiempo buscando para darle respuesta a tu pregunta, te regalamos la soluciones de modo que esperamos resultarte de gran ayuda.

Solución:

Para mí es obvio que la segunda vía debería ser más rápida, pero ¿cuánto?

Así que escribí algunos puntos de referencia aquí.

@State(Scope.Thread)
open class ModifyingImmutableList 

    @Param("10", "100", "10000", "1000000")
    var size: Int = 0

    lateinit var players: List

    @Setup
    fun setup() 
        players = generatePlayers(size)
    

    @Benchmark fun iterative(): List 
        return players.mapIndexed  i, player ->
            if (i == 2) player.copy(score = 100)
            else player
        
    

    @Benchmark fun toMutable(): List 
        val updatedPlayer = players[2].copy(score = 100)
        val mutable = players.toMutableList()
        mutable.set(2, updatedPlayer)
        return mutable.toList()
    

    @Benchmark fun toArrayList(): List 
        val updatedPlayer = players[2].copy(score = 100)
        return players.set(2, updatedPlayer)
    

Y obtuve los siguientes resultados:

$ java -jar target/benchmarks.jar -f 5 -wi 5 ModifyingImmutableList
Benchmark                            (size)   Mode  Cnt         Score        Error  Units
ModifyingImmutableList.iterative         10  thrpt  100   6885018.769 ± 189148.764  ops/s
ModifyingImmutableList.iterative        100  thrpt  100    877403.066 ±  20792.117  ops/s
ModifyingImmutableList.iterative      10000  thrpt  100     10456.272 ±    382.177  ops/s
ModifyingImmutableList.iterative    1000000  thrpt  100       108.167 ±      3.506  ops/s
ModifyingImmutableList.toArrayList       10  thrpt  100  33278431.127 ± 560577.516  ops/s
ModifyingImmutableList.toArrayList      100  thrpt  100  11009646.095 ± 180549.177  ops/s
ModifyingImmutableList.toArrayList    10000  thrpt  100    129167.033 ±   2532.945  ops/s
ModifyingImmutableList.toArrayList  1000000  thrpt  100       528.502 ±     16.451  ops/s
ModifyingImmutableList.toMutable         10  thrpt  100  19679357.039 ± 338925.701  ops/s
ModifyingImmutableList.toMutable        100  thrpt  100   5504388.388 ± 102757.671  ops/s
ModifyingImmutableList.toMutable      10000  thrpt  100     62809.131 ±   1070.111  ops/s
ModifyingImmutableList.toMutable    1000000  thrpt  100       258.013 ±      8.076  ops/s

Entonces, estas pruebas muestran que iterar sobre la recopilación aproximadamente 3 ~ 6 veces más lento, que copiar. También proporciono mi implementación: toArray, que parece más eficiente.

En 10 elementos, toArray el método tiene rendimiento 33278431.127 ± 560577.516 operaciones por segundo. Es lento ¿O es extremadamente rápido? Escribo una prueba de “línea de base”, que muestra el costo de la copia Players y mutando array. Resultados interesantes:

@Benchmark fun baseline(): List 
    val updatedPlayer = players[2].copy(score = 100)
    mutable[2] = updatedPlayer;
    return mutable

Donde mutable – solo MutableList, cual es ArrayList.

$ java -jar target/benchmarks.jar -f 5 -wi 5 ModifyingImmutableList
Benchmark                            (size)   Mode  Cnt         Score         Error  Units
ModifyingImmutableList.baseline          10  thrpt  100  81026110.043 ± 1076989.958  ops/s
ModifyingImmutableList.baseline         100  thrpt  100  81299168.496 ±  910200.124  ops/s
ModifyingImmutableList.baseline       10000  thrpt  100  81854190.779 ± 1010264.620  ops/s
ModifyingImmutableList.baseline     1000000  thrpt  100  83906022.547 ±  615205.008  ops/s
ModifyingImmutableList.toArrayList       10  thrpt  100  33090236.757 ±  518459.863  ops/s
ModifyingImmutableList.toArrayList      100  thrpt  100  11074338.763 ±  138272.711  ops/s
ModifyingImmutableList.toArrayList    10000  thrpt  100    131486.634 ±    1188.045  ops/s
ModifyingImmutableList.toArrayList  1000000  thrpt  100       531.425 ±      18.513  ops/s

¡En 10 elementos tenemos una regresión 2x, y en 1 millón aproximadamente 150000x!

Así que parece ArrayList no es la mejor opción para estructuras de datos inmutables. Pero hay muchas otras colecciones, una de ellas es pcollections. Veamos qué obtuvieron en nuestro escenario:

@Benchmark fun pcollections(): List 
    val updatedPlayer = players[2].copy(score = 100)
    return pvector.with(2, updatedPlayer)

Donde es pvector pvector:PVector = TreePVector.from(players).

$ java -jar target/benchmarks.jar -f 5 -wi 5 ModifyingImmutableList
Benchmark                             (size)   Mode  Cnt         Score         Error  Units
ModifyingImmutableList.baseline           10  thrpt  100  79462416.691 ± 1391446.159  ops/s
ModifyingImmutableList.baseline          100  thrpt  100  79991447.499 ± 1328008.619  ops/s
ModifyingImmutableList.baseline        10000  thrpt  100  80017095.482 ± 1385143.058  ops/s
ModifyingImmutableList.baseline      1000000  thrpt  100  81358696.411 ± 1308714.098  ops/s
ModifyingImmutableList.pcollections       10  thrpt  100  15665979.142 ±  371910.991  ops/s
ModifyingImmutableList.pcollections      100  thrpt  100   9419433.113 ±  161562.675  ops/s
ModifyingImmutableList.pcollections    10000  thrpt  100   4747628.815 ±   81192.752  ops/s
ModifyingImmutableList.pcollections  1000000  thrpt  100   3011819.457 ±   45548.403  ops/s

¡Buenos resultados! En 1 millón de casos, solo tenemos una ejecución 27 veces más lenta, lo cual es bastante bueno, pero en colecciones pequeñas. pcollections un poco más lento que ArrayList implementación.

Actualizar: como mencionó @ mfulton26, en toMutable punto de referencia toList es innecesario, así que lo eliminé y volví a ejecutar las pruebas. También agregué un punto de referencia sobre el costo de creación. TreePVector de existente array:

$ java -jar target/benchmarks.jar  ModifyingImmutableList
Benchmark                                 (size)   Mode  Cnt         Score         Error  Units
ModifyingImmutableList.baseline               10  thrpt  200  77639718.988 ± 1384171.128  ops/s
ModifyingImmutableList.baseline              100  thrpt  200  75978576.147 ± 1528533.332  ops/s
ModifyingImmutableList.baseline            10000  thrpt  200  79041238.378 ± 1137107.301  ops/s
ModifyingImmutableList.baseline          1000000  thrpt  200  84739641.265 ±  557334.317  ops/s

ModifyingImmutableList.iterative              10  thrpt  200   7389762.016 ±   72981.918  ops/s
ModifyingImmutableList.iterative             100  thrpt  200    956362.269 ±   11642.808  ops/s
ModifyingImmutableList.iterative           10000  thrpt  200     10953.451 ±     121.175  ops/s
ModifyingImmutableList.iterative         1000000  thrpt  200       115.379 ±       1.301  ops/s

ModifyingImmutableList.pcollections           10  thrpt  200  15984856.119 ±  162075.427  ops/s
ModifyingImmutableList.pcollections          100  thrpt  200   9322011.769 ±  176301.745  ops/s
ModifyingImmutableList.pcollections        10000  thrpt  200   4854742.140 ±   69066.751  ops/s
ModifyingImmutableList.pcollections      1000000  thrpt  200   3064251.812 ±   35972.244  ops/s

ModifyingImmutableList.pcollectionsFrom       10  thrpt  200   1585762.689 ±   20972.881  ops/s
ModifyingImmutableList.pcollectionsFrom      100  thrpt  200     67107.504 ±     808.308  ops/s
ModifyingImmutableList.pcollectionsFrom    10000  thrpt  200       268.268 ±       2.901  ops/s
ModifyingImmutableList.pcollectionsFrom  1000000  thrpt  200         1.406 ±       0.015  ops/s

ModifyingImmutableList.toArrayList            10  thrpt  200  34567833.775 ±  423910.463  ops/s
ModifyingImmutableList.toArrayList           100  thrpt  200  11395084.257 ±   76689.517  ops/s
ModifyingImmutableList.toArrayList         10000  thrpt  200    134299.055 ±     602.848  ops/s
ModifyingImmutableList.toArrayList       1000000  thrpt  200       549.064 ±      15.317  ops/s

ModifyingImmutableList.toMutable              10  thrpt  200  32441627.735 ±  391890.514  ops/s
ModifyingImmutableList.toMutable             100  thrpt  200  11505955.564 ±   71394.457  ops/s
ModifyingImmutableList.toMutable           10000  thrpt  200    134819.741 ±     526.830  ops/s
ModifyingImmutableList.toMutable         1000000  thrpt  200       561.031 ±       8.117  ops/s

De Kotlin List La interfaz es para “acceso de sólo lectura” a listas que no son necesariamente listas inmutables. La inmutabilidad no se puede imponer a través de interfaces. La implementación actual de stdlib de Kotlin para toList llamadas, en algunos casos, toMutableList y devuelve su resultado como un “acceso de solo lectura” List.

Si tienes un List de jugadores y desea obtener de manera eficiente otra List de jugadores con un elemento actualizado, una solución sencilla es copiar la lista a un MutableList, actualice el elemento deseado y luego solo almacene una referencia a la lista resultante usando el “acceso de solo lectura” de Kotlin List interfaz:

val updatedPlayers: List = players.toMutableList().apply 
    this[2] = updatedPlayer

Si esto es algo que tiene la intención de hacer con frecuencia, podría considerar crear una función de extensión para encapsular los detalles de la implementación:

inline fun  List.copy(mutatorBlock: MutableList.() -> Unit): List 
    return toMutableList().apply(mutatorBlock)

Luego, puede copiar listas con actualizaciones con mayor fluidez (similar a la copia de clases de datos) sin necesidad de especificar explícitamente el tipo de resultado:

val updatedPlayers = players.copy  this[2] = updatedPlayer 

Si haces scroll puedes encontrar los informes de otros usuarios, tú además tienes la opción de dejar el tuyo si lo deseas.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *