Ejecución de trabajos en paralelo en Ubuntu: diferencias de contención de E/S entre Perl y Java

Ejecución de trabajos en paralelo en Ubuntu: diferencias de contención de E/S entre Perl y Java

Disculpas si esto está fuera de tema: se trata de la eficiencia relativa de ejecutar scripts Perl/Java con muchas E/S en paralelo en un sistema Ubuntu.

He escrito dos versiones simples de un script de copia de archivos (Perl y Java); ver más abajo. Cuando ejecuto los scripts en un archivo de 15 GB, cada uno toma una cantidad de tiempo similar en una máquina de 48 núcleos que ejecuta Ubuntu Server 12.04 (perl 2m10s, java 2m27s).

Sin embargo, cuando ejecuto seis instancias en paralelo, cada una operando en un archivo de entrada diferente de 15 GB, observo tiempos de procesamiento muy diferentes:

  • Perl: una instancia se completa en 2m6s, todas las demás tardan 27m26s - 28m10s.
  • Java: todas las instancias tardan entre 3m27s y 4m37s.

Al observar los núcleos del procesador durante toplos procesos Perl de larga duración, veo que los núcleos ocupados tienen porcentajes de espera de E/S (%wa) de 70%+, lo que implica algún tipo de contención de disco (todos los archivos están en un disco duro). Presumiblemente, entonces, Java BufferedReaderes de alguna manera menos sensible a esta contención de disco.

Pregunta: ¿Parece esto una conclusión razonable? Y si es así, ¿alguien puede sugerir alguna acción que pueda realizar a nivel del sistema operativo o en Perl para que el script Perl sea tan eficiente como Java para este tipo de tarea?

Nota: mi objetivo no es simplemente copiar archivos; mis scripts reales contienen lógica adicional, pero exhiben el mismo comportamiento de rendimiento que los scripts simplificados a continuación.

perla

#!/usr/bin/perl -w
open(IN, $ARGV[0]) || die();
open(OUT, ">$ARGV[1]") || die();
while (<IN>) {
    print OUT $_
}
close(OUT);
close(IN);

Java

import java.io.*;
public class CopyFileLineByLine {
    public static void main(String[] args) throws IOException {
        BufferedReader br = null;
        PrintWriter pw = null;
        try {
            br = new BufferedReader(new FileReader(new File(args[0])));
            pw = new PrintWriter(new File(args[1]));
            String line;
            while ((line = br.readLine()) != null) {
                pw.println(line);
            }
        }
        finally {
            if (pw != null) pw.close();
            if (br != null) br.close();
        }
    }
}

Respuesta1

Lo más probable es que la diferencia de rendimiento resida en cómo funciona el almacenamiento en búfer entre Perl y Java. En este caso, utilizó un bufferedReader en Java, lo que le da una ventaja. Perl almacena en buffer alrededor de 4k desde el disco.

Podrías probar algunas cosas aquí. Una es utilizar la función de lectura en Perl para obtener bloques más grandes a la vez. Esopuedemejorar el rendimiento.

Otra opción podría ser investigar los distintos módulos de Perl relacionados con mmap.

Respuesta2

Realmente no es una respuesta, pero el código no se formatea bien en los comentarios.

Para GNU Parallel uso una versión de esto para copiar. Puede entregar pedidos de 1 GB/s/núcleo y funciona bien en paralelo:

perl -e '$left=-s STDIN;
  while($read=sysread(STDIN,$buf,$left>131072?131072:$left)){
    $left-=$read;
    syswrite(STDOUT,$buf);
  }' < in > out

Respuesta3

Hola, puede que este no sea el caso, pero desde la primera observación, su script Perl se ejecuta de forma secuencial e interpretada. Mientras su programa Java se ejecuta como un programa compilado y lo hace de forma paralela. Esto puede explicar la diferencia en la velocidad de finalización.

información relacionada