Porque
Uno de mis proyectos está escrito en Kotlin. Es una aplicación de base de datos multidimensional, por lo que operará la matriz int con mucha frecuencia. Si hay un segmento del programa, necesita realizar cientos de millones de acciones de limpieza de matriz, similar a este código:
Arrays.fill (objetivo, 0);
Este Arrays.fill es en realidad una implementación de jdk, muy simple, es un bucle for para llenar los datos.
Así que quiero mejorarlo, escribir la longitud de la matriz común en una sola implementación, por ejemplo, el método para borrar 8 longitudes es el siguiente:
fun clear8 (target: IntArray) { if (target.size <8 ) { throw IndexOutOfBoundsException () } target [ 0] = 0 target [ 1] = 0 target [ 2] = 0 target [ 3] = 0 target [ 4] = 0 objetivo [ 5] = 0 objetivo [ 6] = 0 objetivo [ 7] = 0 }
No dudes de tus ojos, tal escritura suele ser efectiva. Un buen compilador optimizará el código que escribí. Por supuesto, un mejor compilador optimizará el bucle for de una matriz simple.
Entonces probémoslo.
import java.util. * import kotlin.system.measureNanoTime fun main () { test3 () } private fun test3 () { val size = 8 val time2 = measureNanoTime { val target = IntArray (size) for (i en 0 hasta 10_0000_0000 ) { IntArrays.clear8 (target) } } println ( "fill $ size $ time2" ) val time1 = measureNanoTime { val target = IntArray (size) para (i en 0hasta 10_0000_0000) { Arrays.fill (target, 0 ) } } println ( "Arrays.fill $ size $ time1" ) println () } objeto interno IntArrays { fun clear8 (target: IntArray) { if (target.size <8 ) { throw IndexOutOfBoundsException () } objetivo [ 0] = 0 objetivo [ 1] = 0 objetivo [ 2] = 0 objetivo [ 3] = 0 objetivo [ 4] = 0 objetivo [ 5] = 0 objetivo [ 6] = 0 objetivo [ 7] = 0 } }
Resultados de la prueba:
fill8 55,408,200
Arrays.fill8 2,262,171,100
¡Se puede ver que, usando el método de despliegue, el rendimiento es 40 veces mayor que los 2.2 segundos que viene con Java! !
Comparación de rendimiento con Java
Lamento que el compilador de Kotlin sea realmente fuerte, pero piénselo detenidamente, no está bien, Kotlin está basado en la JVM, y el crédito debería ser que el tiempo de ejecución de la máquina virtual de Java es muy poderoso, por lo que si este programa se convierte a Java, es mejor escribir directamente Rendimiento rápido, al menos consistente. Solo hazlo.
// IntArrays.java import java.util.Arrays; Clase final IntArrays { static void clear8 ( int [] target) { / * if (target.length <8) { throw new IndexOutOfBoundsException (); } * / target [ 0] = 0 ; objetivo [ 1] = 0 ; objetivo [ 2] = 0 ; objetivo [ 3] = 0 ; objetivo [ 4] = 0 ; objetivo [ 5] = 0 ; objetivo [ 6] = 0 ; objetivo [ 7] = 0 ; } } // IntArraysDemoJava.java import java.util.Arrays; public final class IntArraysDemoJava { public static void main (String [] var0) { test1 (); } Privada estática vacío test1 () { larga count = 1000000000 ; inicio largo = System.nanoTime (); final int [] target = nuevo int [8 ]; para ( inti = 0; yo <cuento; i ++ ) { IntArrays.clear8 (objetivo); } tiempo largo2 = System.nanoTime () - inicio; System.out.println ( "fill8" + tiempo2); inicio = System.nanoTime (); for ( int i = 0; i <count; i ++ ) { Arrays.fill (target, 0 ); } long time1 = System.nanoTime () - inicio; System.out.println ( "Arrays.fill8" + tiempo1); System.out.println (); } }
Los resultados de la prueba son los siguientes:
fill8 2,018,500,800
Arrays.fill8 2,234,306,500
Dios mío, este tipo de optimización casi no tiene efecto en Java. No encontré ningún concepto de parámetros de compilación de lanzamiento. A lo sumo, solo tiene debug = false. Lo incluí en gradle.
compileJava { options.debug = false }
Entonces, ¿eso significa que el código de bytes generado por Kotlin es mejor que el código de bytes generado por Java?
Java Kotlin ALOAD 0 ALOAD 1 ICONST_0 ICONST_0 ICONST_0 ICONST_0 IASTORE ASTORE ALOAD 0 ALOAD 1 ICONST_1 ICONST_1 ICONST_0 ICONST_0 IASTORE IASTORE
El código de bytes es ligeramente diferente, si me preguntas por qué. Mi gallina . . . . .
Comparación con C #
Como fanático incondicional de .net, esta vez pensaré si c # es más rápido, sin mencionar que .net core 3 ha optimizado mucho el rendimiento,
Programa de clase { static void Main ( string [] args) { Test3.test1 (); } } class Test3 { public static void test1 () { cuenta larga = 1000000000 ; var watch = System.Diagnostics.Stopwatch.StartNew (); int [] target = nuevo int [ 8 ]; para ( int i = 0 ; i <cuenta; i ++ ) { Clear8 (target); } watch.Stop (); Console.WriteLine ( " fill8 " + watch.Elapsed); watch.Restart (); for ( int i = 0 ; i <count; i ++ ) { Array.Clear (target, 0 , 8 ); } watch.Stop (); Console.WriteLine ( " Array.Clear8 " + watch.Elapsed); Console.WriteLine (); } estático vacío Clear8 ( int [] objetivo) { / *if (target.Length <8) { lanzar nuevo IndexOutOfRangeException (); } * / target [ 0 ] = 0 ; objetivo [ 1 ] = 0 ; objetivo [ 2 ] = 0 ; objetivo [ 3 ] = 0 ; objetivo [ 4 ] = 0 ; objetivo [ 5 ] = 0 ; objetivo [ 6 ] = 0 ; objetivo [ 7 ] = 0; } }
Resultados de la prueba:
fill8 00: 00: 02.7462676
Array.Clear8 00: 00: 08.4920514
En comparación con Java, es aún más lento, e incluso el Array.clear que viene con el sistema es aún más lento. ¿Cómo puedo soportarlo? Entonces, Span.Fill (0), que es un pase, es aún menos satisfactorio.
Rendimiento comparado con Nim
Se menciona el interés, luego use el lenguaje C para lograr uno ... No está escrito, soy estúpido ..., luego use Rust para implementar uno, o no se da cuenta, siga el tutorial paso a paso, Todavía no hecho ...
Finalmente lanzando un entorno Nim, um, sigue siendo simple.
tiempos de importación , strutils proc clear8 * [int] (target: var seq [int]) = target [0] = 0 target [ 1] = 0 target [ 2] = 0 target [ 3] = 0 target [ 4] = 0 target [ 5] = 0 target [ 6] = 0 target [ 7] = 0 proc clear * [int] (target: var seq [int]) = para i en 0 .. < target.len: target [i] = 0 proc test3 () = const size = 8 var start = epochTime () var target = newseq [int] (size) para i en 0 .. < 10_0000_0000: target.clear8 () let elapsedStr = (epochTime () - start) .formatFloat (format = ffDecimal , precisión = 3 ) echo " fill8 " , elapsedStr start = epochTime () para i en 0 .. < 10_0000_0000: target.clear () let elapsedStr2= (epochTime () - start) .formatFloat (format = ffDecimal, precision = 3 ) echo " Arrays.fill " , elapsedStr2 test3 ()
Para los resultados de la prueba, preste atención a agregar el parámetro --release.
fill8 3.499
Arrays.fill 5.825
Decepción y su decepción.
Observaciones
Todas las pruebas se realizaron en mi computadora de escritorio y la configuración es la siguiente:
AMD Ryzen 5 3600 6 Core 3.59 Ghz
8 GB de RAM
Windows 10 64 Professional Edition
Todas las pruebas se compilan con el lanzamiento.