paginacion memoria ejecuta cuando como collector codigo aprender java performance memory hardware benchmarking

java - ejecuta - Programa que supera la tasa de transferencia de memoria teórica



garbage collector java (4)

Aquí hay varias cosas en el trabajo.

En primer lugar: la fórmula para la tasa de transferencia de memoria de DDR3 es

memory clock rate × 4 (for bus clock multiplier) × 2 (for data rate) × 64 (number of bits transferred) / 8 (number of bits/byte) = memory clock rate × 64 (in MB/s)

Para DDR3-1066 (que tiene 133⅓ MHz reloj de 133⅓ MHz ), obtenemos un ancho de banda de memoria teórica de 8533⅓ MB/s o 8138.02083333... MiB/s para un solo canal, y 17066⅔ MB/s o 16276.0416666... MiB/s para doble canal.

Segundo: la transferencia de una gran porción de datos es más rápida que la transferencia de muchas porciones pequeñas de datos.

Tercero: puedes ignorar los efectos de caché, que pueden ocurrir.

Cuarto: si realiza mediciones de tiempo, debe usar System.nanoTime() . Este método es más preciso.

Aquí hay una versión reescrita del programa de prueba 1 .

import java.util.Random; public class Main { public static void main(String... args) { final int SIZE = 1024 * 1024 * 1024; final int RUNS = 8; final int THREADS = 8; final int TSIZE = SIZE / THREADS; assert (TSIZE * THREADS == THREADS) : "TSIZE must divide SIZE!"; byte[] src = new byte[SIZE]; byte[] dest = new byte[SIZE]; Random r = new Random(); long timeNano = 0; Thread[] threads = new Thread[THREADS]; for (int i = 0; i < RUNS; ++i) { System.out.print("Initializing src... "); for (int idx = 0; idx < SIZE; ++idx) { src[idx] = ((byte) r.nextInt(256)); } System.out.println("done!"); System.out.print("Starting test... "); for (int idx = 0; idx < THREADS; ++idx) { final int from = TSIZE * idx; threads[idx] = new Thread(() -> { System.arraycopy(src, from, dest, 0, TSIZE); }); } long start = System.nanoTime(); for (int idx = 0; idx < THREADS; ++idx) { threads[idx].start(); } for (int idx = 0; idx < THREADS; ++idx) { try { threads[idx].join(); } catch (InterruptedException e) { e.printStackTrace(); } } timeNano += System.nanoTime() - start; System.out.println("done!"); } double timeSecs = timeNano / 1_000_000_000d; System.out.println("Transfered " + (long) SIZE * RUNS + " bytes in " + timeSecs + " seconds."); System.out.println("-> " + ((long) SIZE * RUNS / timeSecs / 1024 / 1024 / 1024) + " GiB/s"); } }

De esta manera, mitiga la mayor cantidad posible de "otros cálculos" y mide (casi) solo la velocidad de copia de memoria a través de System.arraycopy(...) . Este algoritmo todavía puede tener problemas con respecto al almacenamiento en caché.

Para mi sistema (Dual Channel DDR3-1600), obtengo algo alrededor de 6 GiB/s , mientras que el límite teórico es de alrededor de 25 GiB/s (incluido DualChannel).

Como lo señaló MagicM18 , la JVM introduce algunos gastos generales. Por lo tanto, se espera que no puedas alcanzar el límite teórico.

1 Sidenote: para ejecutar el programa, uno debe darle a la JVM más espacio en el montón. En mi caso, 4096 MB fueron suficientes.

Tengo una computadora portátil con CPU Intel Core 2 Duo de 2.4GHz y módulos DDR3 de 2x4Gb y 1066MHz.

Espero que esta memoria pueda funcionar a una velocidad de 1067 MiB / seg, y mientras haya dos canales, la velocidad máxima es de 2134 MiB / seg .

Hice una pequeña aplicación de Java para probar que:

private static final int size = 256 * 1024 * 1024; // 256 Mb private static final byte[] storage = new byte[size]; private static final int s = 1024; // 1Kb private static final int duration = 10; // 10sec public static void main(String[] args) { long start = System.currentTimeMillis(); Random rnd = new Random(); byte[] buf1 = new byte[s]; rnd.nextBytes(buf1); long count = 0; while (System.currentTimeMillis() - start < duration * 1000) { long begin = (long) (rnd.nextDouble() * (size - s)); System.arraycopy(buf1, 0, storage, (int) begin, s); ++count; } double totalSeconds = (System.currentTimeMillis() - start) / 1000.0; double speed = count * s / totalSeconds / 1024 / 1024; System.out.println(count * s + " bytes transferred in " + totalSeconds + " secs (" + speed + " MiB/sec)"); byte[] buf2 = new byte[s]; count = 0; start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < duration * 1000) { long begin = (long) (rnd.nextDouble() * (size - s)); System.arraycopy(storage, (int) begin, buf2, 0, s); Arrays.fill(buf2, (byte) 0); ++count; } totalSeconds = (System.currentTimeMillis() - start) / 1000.0; speed = count * s / totalSeconds / 1024 / 1024; System.out.println(count * s + " bytes transferred in " + totalSeconds + " secs (" + speed + " MiB/sec)"); }

Esperaba que el resultado fuera inferior a 2134 MiB / seg, pero tengo lo siguiente:

17530212352 bytes transferred in 10.0 secs (1671.811328125 MiB/sec) 31237926912 bytes transferred in 10.0 secs (2979.080859375 MiB/sec)

¿Cómo es posible que la velocidad fuera de casi 3 GiB / seg?


En Wikipedia hay una tabla de tasas de transferencia . Este portátil en particular tiene las siguientes especificaciones:

  • Tipo de módulo: PC3-8500 DDR3 SDRAM
  • Tipo de chip: DDR3-1066
  • Reloj de memoria: 133 MHz
  • Velocidad del bus: 1.066GT / s
  • Velocidad de transferencia (bits / s): 64 Gbit/s
  • Velocidad de transferencia (bytes decimales / s): 8 GB/s

Esto es por módulo DDR3 único por canal único.


Esto podría ser una cuestión de configuración de hardware. Según la información proporcionada, hay dos núcleos y dos módulos de memoria, pero el número de canales de memoria no está claro. Si bien nunca he visto que se realicen pruebas a escala de una computadora portátil, en sistemas más grandes, la configuración de módulos DIMM en los canales de memoria puede tener un impacto significativo en las tasas de transferencia de memoria.

Por ejemplo, en servidores modernos es posible tener configuraciones de memoria de un DIMM por canal (ODPC) o dos DIMM por canal (TDPC). Cada CPU física puede tener múltiples canales de memoria divididos entre los núcleos físicos en dicha CPU, y cada servidor podría tener múltiples CPU físicas (generalmente 2-4 en servidores modernos).

La forma en que se distribuye la memoria entre estos canales, los núcleos y las CPU / chips pueden tener un impacto significativo en el rendimiento de la memoria en función de lo que se esté midiendo. Por ejemplo, los sistemas con una configuración ODPC tendrán tiempos de transferencia significativamente mejorados (en términos de Transferencias por segundo o MegaTransferencias por segundo, MT / s) en comparación con los sistemas que tienen una configuración TDPC en los casos en que la cantidad de memoria (en GB) en El sistema TDPC es igual o mayor que la cantidad de memoria en la configuración ODPC.

Sobre la base de este conocimiento, es concebible que una computadora portátil que está configurada con 2 canales de memoria en un ODPC y un canal por cada núcleo pueda, en teoría, lograr el rendimiento descrito.

Con todo lo que se dice, hay una serie de herramientas de análisis y creación de perfiles de memoria preempaquetadas que se pueden ejecutar de forma no invasiva para obtener información sobre el rendimiento de la memoria en su sistema. Memtest es una herramienta muy poderosa, bien entendida y bien documentada para probar la memoria. Se puede descargar en un disco de arranque de algún tipo (USB, DVD, disquete, etc.) que se puede usar de manera segura para agotar la memoria de un sistema sin la posibilidad de dañar o alterar el sistema operativo. También se incluye en el DVD de instalación para algunas distribuciones de Linux, así como en los DVD / imágenes de rescate. Es una herramienta muy poderosa que he usado en muchas ocasiones para depurar y analizar el rendimiento de la memoria, aunque normalmente en servidores.


Su método de prueba está mal diseñado en muchos aspectos, así como su interpretación de la calificación de RAM.

Vamos a empezar con la calificación; desde la introducción de SDRam, marketing nombra los módulos después de su especificación de bus, es decir, la frecuencia del reloj del bus, junto con la tasa de transferencia de ráfagas. Ese es el mejor de los casos, y en la práctica no puede sostenerse continuamente.

Los parámetros omitidos por esa etiqueta son el tiempo de acceso real (también conocido como latencia) y el tiempo de ciclo total (también conocido como tiempo de precarga). Estos pueden resolverse observando las especificaciones de "sincronización" (las cosas 2-3-3). Busque un artículo que explique esas cosas en detalle. En realidad, la CPU no suele transferir bytes individuales, sino líneas de caché completas (por ejemplo, 8 entradas por 8 bytes = 64 bytes).

Su código de prueba está mal diseñado, ya que está haciendo un acceso aleatorio con un bloque relativamente pequeño no alineado con los límites de los datos reales. Este acceso aleatorio también incurre en faltas de página frecuentes en la MMU (conozca qué es el TLB / hace). Así que estás midiendo una mezcla salvaje de diferentes aspectos del sistema.