package ar.com.javacuriosities.nio.zero_copy; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.nio.channels.FileChannel; import java.util.Arrays; /* * Zero copy es una técnica que nos permite evitar la copia * redundante de datos entre buffers intermedios y asi reducir * el numero de context switch en la aplicación entre el user-space * y kernel-space. * * Enviar datos de un archivo a un socket * * Version sin Zero Copy: * * 1- Al ejecutar un read() se genera un context switch del User mode al Kernel mode, internamente se ejecuta * un sys_read(), lo que copia la información del disco a un Read buffer del Kernel. * 2- Se ejecuta otro context switch y ahora se copian los datos del buffer desde el Kernel al User space y ahí termina el llamado * al read(). * 3- Ahora al ejecutar un send() para el Socket se genera otro context switch al Kernel mode y una tercera copia de los * datos es realizada para dejarlos en el buffer que utiliza el Socket * 4- Ahora el llamado a send() retorna y en paralelo y de forma asincronica una cuarta copia es realizada desde el Socket buffer al NIC buffer por medio de DMA * * Se ejecutan 4 context switch, 2 copias por DMA y 2 copias por el CPU * * Usando Zero copy: * * 1- Al ejecutar un transferTo() se genera un context switch del User mode al Kernel mode, y se copia la información * a un Read buffer del Kernel por medio de DMA * 2- Ahora se copia la información del Read buffer directamente al Socket Buffer (Esta copia la hace el CPU) y luego una tercera copia al NIC buffer usando DMA * * Se ejecutan 2 context switch, 2 copias por DMA y 1 copia por el CPU * * Usando Zero copy (Hardware con soporte para Scatter/Gather operations): * * 1-Al ejecutar un transferTo() se genera un context switch del User mode al Kernel mode, y se copia la información * a un buffer del Kernel por medio de DMA. * 2- Ahora en lugar de copiar la información del buffer del Kernel a otro lado, se agrega la información con la ubicación y longitud de los datos, el DMA engine * se encarga de pasar esta información al engine del protocolo * * Se ejecuta 1 context switch y 2 copias por DMA * * Uso: * * Para utilizar zero copy desde Java podemos utilizar los métodos transferTo y transferFrom * * Limitaciones: * * - Solo es aplicable a contenido estático * - No podemos aplicar si los datos tienen que ser modificados o con algún agregado adicional * - FileChannel solo puede ser usado para transferir datos de File -> File o File -> Socket * */ public class ZeroCopy { private static final int BUFFER_SIZE = 8 * 1024; public static void main(String[] args) { try { // Creamos un archivo grande para la prueba, hay que incrementar el tamaño para ir viendo la diferencia File bigFile = createBigFile(100); File outputNormalCopy = new File("output_normal_copy.txt"); File outputZeroCopy = new File("output_zero_copy.txt"); outputNormalCopy.deleteOnExit(); outputZeroCopy.deleteOnExit(); copy(bigFile, outputNormalCopy); zeroCopy(bigFile, outputZeroCopy); } catch (IOException e) { // Log and Handle exception e.printStackTrace(); } } public static File createBigFile(int sizeInMb) throws IOException { File file = File.createTempFile("data", ".txt"); file.deleteOnExit(); char[] chars = new char[1024]; Arrays.fill(chars, 'A'); String longLine = new String(chars); long startTime = System.nanoTime(); PrintWriter printWriter = new PrintWriter(new FileWriter(file)); for (int i = 0; i < sizeInMb * 1024; i++) { printWriter.println(longLine); } printWriter.close(); long totalTime = System.nanoTime() - startTime; System.out.printf("Took %.3f seconds to write to a %d MB, file rate: %.1f MB/s%n", totalTime / 1e9, file.length() >> 20, file.length() * 1000.0 / totalTime); return file; } public static void copy(File from, File to) throws IOException { long startTime = System.nanoTime(); byte[] data = new byte[BUFFER_SIZE]; long bytesCopied = 0; long bytesToCopy = from.length(); try (FileInputStream fis = new FileInputStream(from); FileOutputStream fos = new FileOutputStream(to)) { while (bytesCopied < bytesToCopy) { fis.read(data); fos.write(data); bytesCopied += data.length; } fos.flush(); } long totalTime = System.nanoTime() - startTime; System.out.printf("Copy - Took %.3f seconds%n", totalTime / 1e9); } public static void zeroCopy(File from, File to) throws IOException { long startTime = System.nanoTime(); try (FileInputStream fis = new FileInputStream(from); FileOutputStream fos = new FileOutputStream(to)) { FileChannel sourceChannel = fis.getChannel(); FileChannel destinationChannel = fos.getChannel(); sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel); } long totalTime = System.nanoTime() - startTime; System.out.printf("Zero Copy - Took %.3f seconds%n", totalTime / 1e9); } }