2009-08-09 21 views
5

Entiendo que tanto Java como Perl intentan bastante difícil encontrar un tamaño de búfer predeterminado de tamaño único para leer en archivos, pero sus elecciones son cada vez más anticuadas y tengo un problema al cambiar la opción predeterminada cuando viene a Perl.¿Cómo puedo configurar el tamaño del búfer de lectura de archivos en Perl para optimizarlo para archivos grandes?

En el caso de Perl, que creo utiliza 8K almacenamientos intermedios por defecto, similar a la elección de Java, no puedo encontrar una referencia utilizando el motor de búsqueda perldoc (realmente Google) sobre cómo aumentar el buffer de entrada de archivo predeterminado tamaño para decir, 64K.

Desde el enlace de arriba, para mostrar cómo 8K tampones no escalan:

Si las líneas suelen tener alrededor de 60 caracteres cada una, entonces el archivo 10000 línea cuenta con unos 610.000 caracteres en ella. Leer el archivo línea por línea con almacenamiento en búfer solo requiere 75 llamadas al sistema y 75 espera para el disco, en lugar de 10,001.

Así que para un archivo de 50.000.000 línea con 60 caracteres por línea (incluyendo la nueva línea al final), con un buffer de 8 K, que va a hacer 366.211 llamadas al sistema para leer un archivo 2.8GiB. Como comentario adicional, puede confirmar este comportamiento mirando el i/o de lectura delta (en Windows al menos, arriba en * nix muestra lo mismo de alguna manera también estoy seguro) en la lista de procesos del administrador de tareas como su programa Perl toma 10 minutos para leer en un archivo de texto :)

Alguien hizo la pregunta sobre aumentar el tamaño del búfer de entrada Perl en perlmonks, alguien respondió here que podría aumentar el tamaño de "$ /", y así aumentar el tamaño del búfer Sin embargo, desde el perldoc:

Configuración $/a una referencia a un número entero, escalar que contiene un número entero, o escalar que es convertible en un entero intentará leer los registros en lugar de líneas, con el tamaño máximo del registro de ser el referencia d entero.

así que supongo que esto no aumenta realmente el tamaño del búfer que utiliza Perl para leer por delante del disco cuando se utiliza la típica:

while(<>) { 
    #do something with $_ here 
    ... 
} 
idioma

"línea por línea".

Ahora podría ser que una versión de "leer un registro a la vez y luego analizarla en líneas" diferente del código anterior sea más rápida en general, y eludir el problema subyacente con el idioma estándar y no poder cambie el tamaño predeterminado del búfer (si eso es realmente imposible), porque podría establecer el "tamaño de registro" en cualquier cosa que desee y luego analizar cada registro en líneas individuales, y esperar que Perl haga lo correcto y termine haciendo un sistema llamada por registro, pero agrega complejidad, y todo lo que quiero hacer es obtener un rendimiento fácil aumentando el almacenamiento intermedio utilizado en el ejemplo anterior a un tamaño razonablemente grande, digamos 64K, o incluso ajustando el tamaño del almacenamiento intermedio al tamaño óptimo para lecturas largas usando una secuencia de comandos de prueba en mi sistema, sin necesidad de complicaciones adicionales.

Las cosas son mucho mejores en Java en lo que respecta al soporte directo para aumentar el tamaño del búfer.

En Java, creo que el tamaño de búfer predeterminado actual que utiliza java.io.BufferedReader también es 8192 bytes, aunque las referencias actualizadas en los documentos JDK son equívocas, por ejemplo, el 1.5 solo dicen:

Se puede especificar el tamaño del búfer o se puede aceptar el tamaño predeterminado. El valor predeterminado es lo suficientemente grande para la mayoría de los propósitos.

suerte con Java que no tiene que confiar en los desarrolladores de JDK de haber tomado la decisión correcta para su aplicación y puede establecer su propio tamaño del búfer (64K en este ejemplo):

import java.io.BufferedReader; 
[...] 
reader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8"), 65536); 
[...] 
while (true) { 
       String line = reader.readLine(); 
       if (line == null) { 
        break; 
       } 
       /* do something with the line here */ 
       foo(line); 
} 

Sólo hay tanto rendimiento que puede exprimir al analizar una línea a la vez, incluso con un gran búfer, y hardware moderno, y estoy seguro de que hay formas de obtener cada onza de rendimiento de la lectura en un archivo leyendo gran cantidad- los registros de línea y dividir cada uno en tokens y luego hacer cosas con esos tokens una vez por registro, pero agregan complejidad y casos extremos (aunque si hay una solución elegante en Java puro (solo con las características presentes en JDK 1.5) que sería genial conocer. Aumentar el tamaño del búfer en Perl resolvería el 80% del problema de rendimiento para Perl al menos, mientras se mantienen las cosas simples.

Mi pregunta es:

¿Hay una manera de ajustar que el tamaño de búfer en Perl para el lenguaje anterior típica "línea por línea", similar forma en que el tamaño del búfer se incrementó en el ejemplo de Java?

Respuesta

6

Puede afectar el almacenamiento temporal, en el supuesto de que se está ejecutando en un O/S que soporta setvbuf. Consulte la documentación para IO::Handle. No tiene que crear explícitamente un objeto IO :: Handle como en la documentación si está usando perl 5.10; todos los identificadores son implícitamente IO :: Handles desde esa versión.

use 5.010; 
use strict; 
use warnings; 

use autodie; 

use IO::Handle '_IOLBF'; 

open my $handle, '<:utf8', 'foo'; 

my $buffer; 
$handle->setvbuf($buffer, _IOLBF, 0x10000); 

while (my $line = <$handle>) { 
    ... 
} 
+0

Sería bueno publicar un enlace a más información sobre los controladores Perl 5.10. –

+0

Lo único diferente de las versiones anteriores es que los identificadores están bendecidos en el paquete IO :: Handle. Esa es la única diferencia. En particular, simplemente abrir un archivo no significa que pueda invocar ningún método en el manejador. Tienes que "usar IO :: Handle" para que los métodos se definan. –

+0

Eso no es nuevo en 5.10; Filehandles han sido bendecidos en IO :: Handle durante mucho tiempo (o, para compatibilidad con versiones anteriores, en FileHandle si eso estaba cargado). Pero como dice Elliot, los métodos no están definidos a menos que use IO :: Handle. – ysth

2

No, no lo es (por debajo de recompilar un Perl modificado), pero se puede leer todo el archivo en la memoria, entonces el trabajo línea por línea a partir de que:

use File::Slurp; 
my $buffer = read_file("filename"); 
open my $in_handle, "<", \$buffer; 
while (my $line = readline($in_handle)) { 
} 

Tenga en cuenta que perl antes de 5.10 por defecto en el uso stdio buffers en la mayoría de los lugares (pero a menudo haciendo trampas y accediendo a los buffers directamente, no a través de la biblioteca stdio), pero en 5.10 y luego se predetermina a su propio sistema de capa de perlio. Este último parece utilizar un buffer 4k de forma predeterminada, pero escribir una capa que permita configurar esto debe ser trivial (una vez que descubra cómo escribir una capa: vea perldoc perliol).

1

Advertencia, el siguiente código solo se ha probado con luz. El siguiente código es una primera toma de una función que le permitirá procesar un archivo línea por línea (de ahí el nombre de la función) con un tamaño de búfer definible por el usuario. Se tarda hasta cuatro argumentos:

  1. un gestor de archivo abierto (por defecto es STDIN)
  2. un tamaño de búfer (por defecto es 4k)
  3. una referencia a una variable para almacenar la línea en (por defecto es $_)
  4. una subrutina anónima para llamar al archivo (el valor predeterminado imprime la línea).

Los argumentos son posicionales, con la excepción de que el último argumento puede ser siempre la subrutina anónima. Las líneas se muerden automáticamente.

errores probables:

  • puede no funcionar en sistemas donde avance de línea es el carácter de fin de línea
  • es probable que fallan cuando se combina con un léxico $_ (introducido en Perl 5.10)

Puede ver en un strace que lee el archivo con el tamaño de búfer especificado. Si me gusta cómo van las pruebas, puede ver esto en CPAN pronto.

#!/usr/bin/perl 

use strict; 
use warnings; 
use Scalar::Util qw/reftype/; 
use Carp; 

sub line_by_line { 
    local $_; 
    my @args = \(
     my $fh  = \*STDIN, 
     my $bufsize = 4*1024, 
     my $ref  = \$_, 
     my $coderef = sub { print "$_\n" }, 
    ); 
    croak "bad number of arguments" if @_ > @args; 

    for my $arg_val (@_) { 
     if (reftype $arg_val eq "CODE") { 
      ${$args[-1]} = $arg_val; 
      last; 
     } 
     my $arg = shift @args; 
     $$arg = $arg_val; 
    } 

    my $buf; 
    my $overflow =''; 
    OUTER: 
    while(sysread $fh, $buf, $bufsize) { 
     my @lines = split /(\n)/, $buf; 
     while (@lines) { 
      my $line = $overflow . shift @lines; 
      unless (defined $lines[0]) { 
       $overflow = $line; 
       next OUTER; 
      } 
      $overflow = shift @lines; 
      if ($overflow eq "\n") { 
       $overflow = ""; 
      } else { 
       next OUTER; 
      } 
      $$ref = $line; 
      $coderef->(); 
     } 
    } 
    if (length $overflow) { 
     $$ref = $overflow; 
     $coderef->(); 
    } 
} 

my $bufsize = shift; 

open my $fh, "<", $0 
    or die "could not open $0: $!"; 

my $count; 
line_by_line $fh, sub { 
    $count++ if /lines/; 
}, $bufsize; 

print "$count\n"; 
+1

empecé a jugar con 'sysread' en respuesta a esta pregunta, pero no pude conseguir feliz sobre cómo analizar las líneas * * después de eso. Esto parece prometedor, pero me pregunto si no será aún más lento que la implementación incorporada de Perl (a pesar del almacenamiento en búfer). – Telemachus

+1

Oye, nunca dije que iba a ser __fácil__, solo que leería los archivos con el tamaño de búfer especificado. Dicho esto, voy a compararlo con el idioma común y los resultados serán parte de los documentos. –

Cuestiones relacionadas