Contexto

Estoy escribiendo un programa Java que se comunica con un programa C # a través de entrada y salida estándar. El programa C # se inicia como un proceso hijo. Obtiene "solicitudes" a través de stdin y envía "respuestas" a través de stdout. Las solicitudes son muy ligeras (tamaño de unos pocos bytes), pero las respuestas son grandes. En una ejecución normal del programa, las respuestas ascienden a aproximadamente 2 GB de datos.

Estoy buscando formas de mejorar el rendimiento y mis mediciones indican que escribir en stdout es un cuello de botella. Aquí están los números de una ejecución normal:

  • Tiempo total: 195 segundos
  • Datos transferidos a través de stdout: 2026 MB
  • Tiempo dedicado a escribir en stdout: 85 segundos
  • rendimiento de salida estándar: 23,8 MB / s

Por cierto, primero escribo todos los bytes en un búfer en memoria y los copio de una vez a la salida estándar para asegurarme de que solo mido el tiempo de escritura de la salida estándar.

Pregunta

¿Cuál es una forma eficiente y elegante de compartir datos entre el proceso hijo de C # y el proceso padre de Java? Está claro que stdout no va a ser suficiente.

He leído aquí y allá sobre cómo compartir memoria a través de archivos mapeados en memoria, pero las API de Java y .NET me dan la impresión de que estoy buscando en el lugar equivocado.

0
aochagavia 7 oct. 2020 a las 19:06

2 respuestas

La mejor respuesta

Como mencionó Matthew Watson en los comentarios, de hecho es posible e increíblemente rápido usar un archivo mapeado en memoria. De hecho, el rendimiento de mi programa pasó de 24 MB / sa 180 MB / s. A continuación se muestra la esencia de la misma.

El siguiente código Java crea el archivo mapeado en memoria utilizado para la comunicación y abre un búfer del que podemos leer:

var path = Paths.get("test.mmap");
var channel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
var mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 200_000 * 8);

El siguiente código C # abre el archivo mapeado en memoria y crea una secuencia que puede usar para escribir bytes en él (tenga en cuenta que buffer es el nombre de la matriz de bytes que se escribirán):

// This code assumes the file has already been created on the Java side
var file = File.Open("test.mmap", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
var memoryMappedFile = MemoryMappedFile.CreateFromFile(file, fileName, 0, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, false);
var stream = memoryMappedFile.CreateViewStream();
stream.Write(buffer, 0, buffer.Length);
stream.Flush();

Por supuesto, es necesario sincronizar de alguna manera Java y C #. En aras de la simplicidad, no incluí eso en el código anterior. En mi código, estoy usando la entrada estándar y la salida estándar para señalar cuándo es seguro leer / escribir.

0
aochagavia 15 oct. 2020 a las 13:27

Antes de invertir más en archivos asignados en memoria o canalizaciones con nombre, primero comprobaría si realmente lee y escribe de manera eficiente. java.lang.Process.getInputStream() utiliza BufferedInputStream, por lo que el lado del lector debería estar bien. Pero en su programa C # probablemente usará Console.Write. El problema aquí es que AutoFlush está habilitado de forma predeterminada. Entonces, cada escritura vacía explícitamente la secuencia. Escribí mi último código C # hace años, por lo que no estoy actualizado. Pero tal vez sea posible establecer la propiedad AutoFlush de Console.Out en falso y vaciar la secuencia manualmente después de varias escrituras.

Si no fuera posible desactivar AutoFlush, la única forma de mejorar el rendimiento con Console sería escribir más texto con una sola escritura.

Otro posible cuello de botella puede ser un caparazón intermedio que tiene que interpretar los datos escritos. Asegúrese de ejecutar el programa C # directamente y no a través de un script o llamando al ejecutor de comandos.

Antes de comenzar a usar archivos mapeados en memoria, primero intentaría simplemente escribir en un archivo. Siempre que tenga suficiente memoria libre que no sea utilizada por sus programas u otros y mientras no haya otros programas con acceso frecuente al disco, el sistema operativo podrá almacenar una gran cantidad de datos escritos dentro del caché del sistema de archivos. . Siempre que su programa Java lea lo suficientemente rápido desde el archivo mientras su programa C # escribe en el archivo, es muy probable que solo algunos o incluso ningún dato deba cargarse desde el disco.

1
rmunge 7 oct. 2020 a las 18:12