Paralleles Ausführen von Jobs unter Ubuntu - Unterschiede bei E/A-Konflikten zwischen Perl und Java

Paralleles Ausführen von Jobs unter Ubuntu - Unterschiede bei E/A-Konflikten zwischen Perl und Java

Entschuldigen Sie, wenn dies nicht zum Thema gehört – es geht um die relative Effizienz der parallelen Ausführung von E/A-intensiven Perl/Java-Skripten auf einem Ubuntu-System.

Ich habe zwei einfache Versionen eines Dateikopierskripts geschrieben (Perl und Java) – siehe unten. Wenn ich die Skripts auf einer 15 GB großen Datei ausführe, dauert jedes auf einem 48-Core-Computer mit Ubuntu Server 12.04 ähnlich lange (Perl 2 min. 10 s, Java 2 min. 27 s).

Wenn ich jedoch sechs Instanzen parallel ausführe und jede davon eine andere 15 GB große Eingabedatei verarbeitet, stelle ich sehr unterschiedliche Verarbeitungszeiten fest:

  • Perl: Eine Instanz ist in 2 Min. und 6 Min. abgeschlossen, alle anderen benötigen 27 Min. und 26 Min. – 28 Min. und 10 Min.
  • Java: Alle Instanzen dauern 3 min 27 min – 4 min 37 min.

Wenn ich mir die Prozessorkerne topwährend der lang laufenden Perl-Prozesse anschaue, sehe ich, dass die belegten Kerne I/O-Warteprozentsätze (%wa) von über 70 % aufweisen, was auf eine Art Festplattenkonflikt hindeutet (alle Dateien befinden sich auf einer Festplatte). Vermutlich BufferedReaderist Java also irgendwie weniger empfindlich gegenüber diesem Festplattenkonflikt.

Frage: Ist das eine vernünftige Schlussfolgerung? Und wenn ja, kann mir jemand Maßnahmen vorschlagen, die ich auf Betriebssystemebene oder in Perl ergreifen kann, um das Perl-Skript für diese Art von Aufgabe so effizient wie Java zu machen?

Hinweis: Mein Ziel besteht nicht einfach darin, Dateien zu kopieren. Meine echten Skripte enthalten zusätzliche Logik, weisen aber dasselbe Leistungsverhalten auf wie die vereinfachten Skripte unten.

Perl

#!/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();
        }
    }
}

Antwort1

Der Leistungsunterschied liegt höchstwahrscheinlich darin, wie das Puffern zwischen Perl und Java funktioniert. In diesem Fall haben Sie einen gepufferten Reader in Java verwendet, was ihm einen Vorteil verschafft. Perl puffert etwa 4 KB von der Festplatte.

Sie könnten hier ein paar Dinge ausprobieren. Eine Möglichkeit ist, die Lesefunktion in Perl zu verwenden, um größere Blöcke auf einmal abzurufen. DasMaiLeistung verbessern.

Eine andere Möglichkeit könnte darin bestehen, die verschiedenen mmap-bezogenen Perl-Module zu untersuchen.

Antwort2

Nicht wirklich eine Antwort, aber der Code lässt sich in Kommentaren nicht gut formatieren.

Für GNU Parallel verwende ich eine Version davon zum Kopieren. Sie kann in der Größenordnung von 1 GB/s/Core liefern und arbeitet gut parallel:

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

Antwort3

Hallo. Das ist vielleicht nicht der Fall, aber auf den ersten Blick läuft Ihr Perl-Skript sequentiell interpretiert. Ihr Java-Programm hingegen läuft als kompiliertes Programm und parallel. Dies kann den Unterschied bei der Ausführungsgeschwindigkeit erklären.

verwandte Informationen