Optimización del rendimiento de Java: comparación del rendimiento de matrices de prueba y listas vinculadas al consultar, agregar y eliminar

Escenas

Use JMH (marco de prueba de micro-benchmark de Java Microbenchmark Harness) en Java para pruebas de rendimiento y optimización:

Use JMH (marco de prueba de micro-benchmark de Java Microbenchmark Harness) en Java para pruebas de rendimiento y optimización - Programmer Sought

Cuando usamos JMH arriba, probamos los resultados de la comparación de matrices y listas vinculadas en Java al insertar encabezados.

Lo siguiente compara la comparación de rendimiento de consultar y agregar/eliminar en la cabeza, el medio y la cola respectivamente, porque el proceso de agregar y eliminar es similar,

Así que prueba la suma.

Matrices y listas enlazadas en Java

formación

Array (Array) es una estructura de datos compuesta por una colección de elementos del mismo tipo, que asigna una pieza de memoria continua para almacenamiento.

La dirección de almacenamiento correspondiente al elemento se puede calcular utilizando el índice del elemento.

La característica "continua" del arreglo determina que su velocidad de acceso sea muy rápida, debido a que se almacena continuamente, por lo que esto determina que su ubicación de almacenamiento sea fija,

Por lo que su velocidad de acceso es muy rápida.

La desventaja es que tiene requisitos de memoria relativamente altos y se debe encontrar una pieza de memoria continua.

Otra desventaja de los arreglos es que la eficiencia de inserción y eliminación es relativamente lenta.Si insertamos o eliminamos un dato en la parte que no es la cola del arreglo, entonces el

Para mover todos los datos después, esto traerá una cierta sobrecarga de rendimiento.La matriz también tiene la desventaja de que su tamaño es fijo y no se puede expandir dinámicamente.

lista enlazada

La lista enlazada (Linked list) es una estructura de datos básica común. Es una lista lineal, pero no almacena datos en un orden lineal.

En su lugar, en cada nodo se almacena un puntero (Pointer) al siguiente nodo. Dado que no tiene que almacenarse en orden, la lista enlazada se puede insertar

Para lograr la complejidad de O(1), es mucho más rápido que otra tabla de secuencia de lista lineal, pero buscar un nodo o acceder a un nodo con un número específico es mucho más rápido

Toma el tiempo O(n), mientras que las complejidades de tiempo correspondientes de las tablas secuenciales son O(logn) y O(1) respectivamente.

Una lista enlazada es una estructura de datos que no requiere un almacenamiento continuo en la memoria.Los elementos de la lista enlazada tienen dos atributos, uno es el valor del elemento y el otro es un puntero.

Este puntero marca la dirección del siguiente elemento.

Las listas enlazadas se dividen principalmente en las siguientes categorías:

lista enlazada simple

La lista enlazada individualmente contiene dos campos, un campo de información y un campo de puntero. Este vínculo apunta al siguiente nodo de la lista y el último nodo apunta a un valor nulo.

Lista doblemente enlazada

Una lista doblemente enlazada también se denomina lista doblemente enlazada. No solo hay punteros al siguiente nodo en la lista doblemente enlazada, sino también punteros al nodo anterior, de modo que

Desde cualquier nodo para visitar el nodo anterior, por supuesto, también puede visitar el siguiente nodo, e incluso la lista enlazada completa.

lista enlazada circular

El primer nodo de la lista enlazada circular es el último nodo y viceversa.

¿Por qué hay listas de enlaces simples y dobles?

Esto comienza con la eliminación de la lista enlazada.Si la lista enlazada unidireccional necesita eliminar elementos, no solo se debe encontrar el nodo eliminado, sino también el nodo eliminado.

El nodo anterior del punto (generalmente llamado predecesor), porque es necesario cambiar el siguiente puntero en el nodo anterior, pero también porque es una lista unidireccional enlazada,

Por lo tanto, el nodo eliminado no almacena la información relevante del nodo anterior, por lo que debemos consultar la lista vinculada nuevamente para encontrar el nodo anterior.

Esto trae ciertos problemas de rendimiento, por lo que hay una lista doblemente enlazada.

Ventajas de la lista enlazada:

1. La tasa de utilización de la memoria de la lista vinculada es relativamente alta, no se requiere espacio de memoria continuo e incluso si hay fragmentación de la memoria, la creación de la lista vinculada no se verá afectada;

2. La velocidad de inserción y eliminación de la lista enlazada es muy rápida y no es necesario mover una gran cantidad de elementos como una matriz;

3. El tamaño de la lista enlazada no es fijo y se puede ampliar fácilmente de forma dinámica.

Desventajas de la lista enlazada

La principal desventaja de la lista enlazada es que no se puede buscar aleatoriamente, se debe recorrer desde la primera, la eficiencia de la búsqueda es relativamente baja y la complejidad temporal de la consulta de la lista enlazada es O(n).

En el lenguaje Java, el representante de una matriz es ArrayList y el representante de una lista enlazada es LinkedList, por lo que usamos estos dos objetos para realizar pruebas.

Nota:

Blog:
Temperamento pícaro autoritario blog_CSDN Blog-C#, Architecture Road, Blogger en SpringBoot

lograr

1. Agregar cabezal de prueba

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2,time = 1,timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5,time = 5,timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Thread)
//头部添加性能测试
public class ArrayListWithLinkedListTestHeaderAdd {

    private static final int maxSize = 10000; //测试循环次数
    private static final int operationSize = 100; //操作次数

    private static ArrayList<Integer> arrayList;
    private static LinkedList<Integer> linkedList;

    public static void main(String[] args) throws RunnerException {
        //启动基准测试
        Options opt = new OptionsBuilder()
                .include(ArrayListWithLinkedListTestHeaderAdd.class.getSimpleName())  //要导入的测试类
                .build();
        //执行测试
        new Runner(opt).run();
    }

    //@Setup作用于方法上,用于测试前的初始化工作
    //启动执行事件
    @Setup
    public void init(){
        arrayList = new ArrayList<Integer>();
        linkedList = new LinkedList<Integer>();
        for (int i = 0; i < maxSize; i++) {
            arrayList.add(i);
            linkedList.add(i);
        }
    }

    //用于回收某些资源
    @TearDown
    public void finish(){

    }

    @Benchmark
    public void addArrayByFirst(Blackhole blackhole){
        for (int i = 0; i < operationSize; i++) {
            arrayList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(arrayList);
    }

    @Benchmark
    public void addLinkedByFirst(Blackhole blackhole){
        for (int i = 0; i < operationSize; i++) {
            linkedList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(linkedList);
    }
}

La salida del resultado de la prueba es la siguiente:

Benchmark                                              Mode  Cnt        Score         Error  Units
ArrayListWithLinkedListTestHeaderAdd.addArrayByFirst   avgt    5  6597432.461 ± 8384921.207  ns/op
ArrayListWithLinkedListTestHeaderAdd.addLinkedByFirst  avgt    5   111880.375 ±  258245.983  ns/op

Se puede ver que el tiempo de finalización promedio de LinkedList es aproximadamente 60 veces más rápido que el tiempo de finalización promedio de ArrayList al insertar la cabeza

2. Agregar en el medio de la prueba

    @Benchmark
    public void addArrayByMiddle(Blackhole blackhole){
        int startIndex = maxSize /2 ;//获取中间值
        for (int i = startIndex; i < (startIndex + operationSize); i++) {
            arrayList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(arrayList);
    }

    @Benchmark
    public void addLinkedByMiddle(Blackhole blackhole){
        int startIndex = maxSize /2 ;//获取中间值
        for (int i = startIndex; i < (startIndex + operationSize); i++) {
            linkedList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(linkedList);
    }

Resultados de la prueba

Benchmark                                              Mode  Cnt        Score         Error  Units
ArrayListWithLinkedListTestMiddleAdd.addArrayByMiddle   avgt    5  6703786.087 ± 9225973.854  ns/op
ArrayListWithLinkedListTestMiddleAdd.addLinkedByMiddle  avgt    5  1064823.250 ±  697306.502  ns/op

Se puede ver que el tiempo promedio de finalización de LinkedList es aproximadamente 7 veces más rápido que el tiempo promedio de finalización de ArrayList durante la inserción intermedia.

3. Agregar al final de la prueba

    @Benchmark
    public void addArrayByTail(Blackhole blackhole){
        int startIndex = maxSize - 1 - operationSize ;
        for (int i = startIndex; i < (maxSize - 1); i++) {
            arrayList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(arrayList);
    }

    @Benchmark
    public void addLinkedByTail(Blackhole blackhole){
        int startIndex = maxSize - 1 - operationSize ;
        for (int i = startIndex; i < (maxSize - 1); i++) {
            linkedList.add(i,i);
        }
        //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题
        blackhole.consume(linkedList);
    }

Resultados de la prueba

Benchmark                                            Mode  Cnt        Score          Error  Units
ArrayListWithLinkedListTestTailAdd.addArrayByTail   avgt    5  6981011.770 ± 10173306.725  ns/op
ArrayListWithLinkedListTestTailAdd.addLinkedByTail  avgt    5  2180224.087 ±   913796.230  ns/op

Se puede ver que el tiempo de finalización promedio de LinkedList es aproximadamente 3 veces más rápido que el tiempo de finalización promedio de ArrayList cuando se realiza la inserción de la cola.

4. Consulta de encabezado de prueba

    @Benchmark
    public void searchArrayByFirst(){
        for (int i = 0; i < operationSize; i++) {
            arrayList.get(i);
        }
    }

    @Benchmark
    public void searchLinkedByFirst(){
        for (int i = 0; i < operationSize; i++) {
            linkedList.get(i);
        }
    }

Resultados de la prueba

Benchmark                                                    Mode  Cnt     Score      Error  Units
ArrayListWithLinkedListTestHeaderSearch.searchArrayByFirst   avgt    5     3.578 ±    0.835  ns/op
ArrayListWithLinkedListTestHeaderSearch.searchLinkedByFirst  avgt    5  4232.832 ± 2019.900  ns/op

5. Consulta en medio de la prueba

    @Benchmark
    public void searchArrayByMiddle(){
        int startIndex = maxSize / 2;
        int endIndex = startIndex + operationSize;
        for (int i = startIndex; i < endIndex; i++) {
            arrayList.get(i);
        }
    }

    @Benchmark
    public void searchLinkedByMiddle(){
        int startIndex = maxSize / 2;
        int endIndex = startIndex + operationSize;
        for (int i = startIndex; i < endIndex; i++) {
            linkedList.get(i);
        }
    }

Resultados de la prueba

Benchmark                                                     Mode  Cnt        Score        Error  Units
ArrayListWithLinkedListTestMiddleSearch.searchArrayByMiddle   avgt    5        4.969 ±      6.568  ns/op
ArrayListWithLinkedListTestMiddleSearch.searchLinkedByMiddle  avgt    5  1131641.772 ± 313408.389  ns/op

Se puede ver que el tiempo de finalización promedio de ArrayList es aproximadamente 230947 veces más rápido que el tiempo de finalización promedio de LinkedList cuando se realizan consultas centrales.

6. Consulta de cola de prueba

    @Benchmark
    public void searchArrayByTail(){
        for (int i = maxSize - operationSize; i < maxSize; i++) {
            arrayList.get(i);
        }
    }

    @Benchmark
    public void searchLinkedByTail(){
        for (int i = maxSize - operationSize; i < maxSize; i++) {
            linkedList.get(i);
        }
    }

Resultados de la prueba

Benchmark                                                 Mode  Cnt     Score     Error  Units
ArrayListWithLinkedListTestTailSearch.searchArrayByTail   avgt    5     5.074 ±   5.384  ns/op
ArrayListWithLinkedListTestTailSearch.searchLinkedByTail  avgt    5  5689.329 ± 563.806  ns/op

Se puede ver que el tiempo de finalización promedio de ArrayList es aproximadamente 1100 veces más rápido que el tiempo de finalización promedio de LinkedList cuando se realizan consultas de cola.

7. Resumen: ¿Es la lista enlazada en Java más de 1000 veces más lenta que la matriz?

Se puede ver que el tiempo de finalización promedio de ArrayList es aproximadamente 1410 veces más rápido que el tiempo de finalización promedio de LinkedList cuando se realiza una consulta principal.

Podemos ver en la evaluación final que después de que se completa la inicialización de datos, cuando realizamos la operación de inserción, especialmente cuando se inserta desde la cabeza,

Debido a que la matriz tiene que mover todos los elementos después de ella, el rendimiento es mucho más bajo que el de la lista enlazada; pero el rendimiento es todo lo contrario cuando se consulta, porque la lista enlazada necesita atravesar la consulta,

Y LinkedList es una lista doblemente enlazada, por lo que el rendimiento de la consulta intermedia es decenas de miles de veces más lento que el de la consulta de matriz (consulta de 100 elementos),

Al consultar en ambos extremos (cabeza y cola), la lista enlazada también es casi 1000 veces más lenta que la matriz (consulta 100 elementos).

Por lo tanto, en escenarios donde hay muchas consultas, debemos intentar usar arreglos, y cuando hay muchas operaciones de adición y eliminación, debemos usar una estructura de lista enlazada.

Supongo que te gusta

Origin blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/131750105
Recomendado
Clasificación