Executando trabalhos em paralelo no Ubuntu - diferenças de contenção de E/S entre Perl e Java

Executando trabalhos em paralelo no Ubuntu - diferenças de contenção de E/S entre Perl e Java

Desculpas se isso estiver fora do tópico - trata-se da eficiência relativa da execução de scripts Perl/Java com alto peso de E/S em paralelo em um sistema Ubuntu.

Escrevi duas versões simples de um script de cópia de arquivo (Perl e Java) - veja abaixo. Quando executo os scripts em um arquivo de 15 GB, cada um leva um tempo semelhante em uma máquina de 48 núcleos executando o Ubuntu Server 12.04 (perl 2m10s, java 2m27s).

No entanto, quando executo seis instâncias em paralelo, cada uma operando em um arquivo de entrada diferente de 15 GB, observo tempos de processamento muito diferentes:

  • Perl: uma instância é concluída em 2m6s, todas as outras levam 27m26s - 28m10s.
  • Java: todas as instâncias levam de 3m27s a 4m37s.

Observando os núcleos do processador durante topos processos Perl de longa duração, vejo que os núcleos ocupados têm porcentagens de espera de E/S (%wa) de 70%+, implicando algum tipo de contenção de disco (todos os arquivos estão em um HD). Presumivelmente, então, o Java BufferedReaderé de alguma forma menos sensível a essa contenção de disco.

Pergunta - Esta parece uma conclusão razoável? E se sim, alguém pode sugerir alguma ação que eu possa realizar no nível do sistema operacional ou em Perl para tornar o script Perl tão eficiente quanto Java para esse tipo de tarefa?

Observação - meu objetivo não é simplesmente copiar arquivos - meus scripts reais contêm lógica adicional, mas exibem o mesmo comportamento de desempenho dos scripts simplificados abaixo.

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

Responder1

A diferença de desempenho provavelmente está no modo como o buffer funciona entre Perl e Java. Nesse caso, você usou A bufferedReader em java, o que lhe dá uma vantagem. Perl armazena em buffer cerca de 4k do disco.

Você poderia tentar algumas coisas aqui. Uma é usar a função read em perl para obter blocos maiores por vez. Quepoderiamelhorar o desempenho.

Outra opção pode ser investigar os vários módulos perl relacionados ao mmap.

Responder2

Não é realmente uma resposta, mas o código não formata bem nos comentários.

Para GNU Parallel eu uso uma versão disso para copiar. Ele pode entregar na ordem de 1 GB/s/núcleo e funciona bem em paralelo:

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

Responder3

Olá Este pode não ser o caso, mas desde a primeira observação seu script Perl está sendo executado de forma interpretada sequencial. Enquanto seu programa Java está sendo executado como um programa compilado e de maneira paralela. Isso pode explicar a diferença na velocidade de conclusão.

informação relacionada