2009-02-13 19 views
14

A menudo tengo una subrutina en Perl que llena una matriz con cierta información. Debido a que también estoy acostumbrado a la piratería en C++, me encuentro a menudo lo hacen como esto en Perl, utilizando referencias:¿Es ineficaz devolver un conjunto completo de una subrutina Perl?

my @array; 
getInfo(\@array); 

sub getInfo { 
    my ($arrayRef) = @_; 
    push @$arrayRef, "obama"; 
    # ... 
} 

en lugar de la versión más sencilla:

my @array = getInfo(); 

sub getInfo { 
    my @array; 
    push @array, "obama"; 
    # ... 
    return @array; 
} 

La razón, Por supuesto, es que no quiero que la matriz se cree localmente en la subrutina y luego se copie a la vuelta.

¿Es correcto? ¿O Perl lo optimiza de todos modos?

+0

Tal vez podría explicar en un nivel superior lo que está tratando de hacer. Puede haber una manera más moderna y perlita de escribir lo que desea que evitaría su problema por completo. O al menos haz que tus intenciones sean más claras. –

+0

@unknown (google): Lo hago a menudo en diferentes contextos. La última vez fue cuando quería leer un archivo en una matriz. Entonces el archivo debe ser abierto, imprimo información sobre qué archivo estoy abriendo en STDERR, modifico un poco las líneas y las presiono sobre la matriz, luego cierro el archivo. – Frank

+0

modificar cómo? como podría hacerse por línea, con una declaración del mapa? encuentro que empujar algo en una matriz es altamente sospechoso, una vez más a favor de otras técnicas. a veces no se puede evitar, aunque –

Respuesta

18

¿Qué tal devolver una referencia de matriz en primer lugar?

sub getInfo { 
    my $array_ref = []; 
    push @$array_ref, 'foo'; 
    # ... 
    return $array_ref; 
} 

my $a_ref = getInfo(); 
# or if you want the array expanded 
my @array = @{getInfo()}; 

Editar de acuerdo con el comentario de dehmann:

También es posible usar una matriz de lo normal en la función y devolver una referencia a la misma.

sub getInfo { 
    my @array; 
    push @array, 'foo'; 
    # ... 
    return \@array; 
}  
+0

¡Esa me parece la mejor solución! – Powerlord

+0

En realidad, ¿qué hay de crear una matriz real en la función, pero que le devuelva una referencia? Perl mantendría viva la matriz creada localmente y devolvería una referencia de manera eficiente. – Frank

+0

@dehmann: buen punto, incorporé tu comentario en mi respuesta, gracias. – user55400

-4

No sé nada sobre Perl, así que esta es una respuesta de idioma neutral.

En cierto sentido, es ineficaz copiar una matriz de una subrutina en el programa de llamada. La ineficiencia surge en la memoria extra utilizada y el tiempo que lleva copiar los datos de un lugar a otro. Por otro lado, para todas las matrices, excepto para las más grandes, es posible que no te importe un comino, y es posible que prefieras copiar matrices por elegancia, groserías o por cualquier otro motivo.

La solución eficiente es que la subrutina pase al programa de llamada la dirección de la matriz. Como digo, no tengo ni idea sobre el comportamiento predeterminado de Perl a este respecto. Pero algunos lenguajes brindan al programador la opción de elegir qué enfoque.

+0

La "dirección de la matriz" en Perl es una referencia. La pregunta es si Perl lo optimiza. –

13

Pasar referencias es más eficiente, pero la diferencia no es tan grande como en C++. Los valores de los argumentos en sí mismos (es decir, los valores en la matriz) siempre se pasan por referencia de todos modos (aunque se copian los valores devueltos).

Pregunta es: ¿Importa? La mayoría de las veces, no es así. Si devuelve 5 elementos, no se preocupe por ello. Si regresa/pasa 100 000 elementos, use referencias. Solo optimícela si se trata de un cuello de botella.

3

Para responder al rumiamiento final, no, Perl no optimiza esto. No puede, en realidad, porque devolver una matriz y devolver un escalar son fundamentalmente diferentes.

Si maneja grandes cantidades de datos o si el rendimiento es una preocupación importante, sus hábitos C le serán útiles: envíe y devuelva referencias a estructuras de datos en lugar de las estructuras mismas para que no las necesiten para ser copiado Pero, como señaló Leon Timmermans, la gran mayoría de las veces, estás tratando con cantidades más pequeñas de datos y rendimiento, no es gran cosa, así que hazlo de la forma que parezca más legible.

8

Si miro a su ejemplo y pensar en lo que quiere hacer Estoy acostumbrado a escribir de esta manera:

sub getInfo { 
    my @array; 
    push @array, 'obama'; 
    # ... 
    return \@array; 
} 

Me parece que versión sencilla cuando necesito regresar grande la cantidad de datos.No es necesario asignar matriz fuera de sub como escribió en su primer fragmento de código porque my lo hace por usted. De todos modos, no debe hacer una optimización prematura como Leon Timmermanssuggest.

2

Esta es la forma en que normalmente devolvería una matriz.

sub getInfo { 
    my @array; 
    push @array, 'foo'; 
    # ... 
    return @array if wantarray; 
    return \@array; 
} 

De esta forma funcionará de la manera que desee, en contextos escalares o de lista.

my $array = getInfo; 
my @array = getInfo; 

$array->[0] == $array[0]; 

# same length 
@$array == @array; 

No trataría de optimizarlo a menos que sepa que es una parte lenta de su código. Incluso entonces usaría puntos de referencia para ver qué subrutina es realmente más rápida.

+0

Entonces no puede obtener el conteo asignando getInfo() a un valor escalar. http://perlmonks.org/?node_id=729965 tiene un interesante debate sobre el uso de wantarray. – daotoad

+0

Estoy de acuerdo, solía usar 'wantarray' hace unos tres años. Lo tuve difícil, es una característica genial. Después de muchos años de experiencia en el proyecto Big Perl con muchos desarrolladores diferentes, he tomado la decisión de que el código contextual es uno de los peores de Perl. –

+1

@daotoad: nunca se puede asumir que una función que devuelve una lista en contexto de lista devolverá su longitud en contexto escalar, ya que eso solo ocurre cuando la función devuelve una matriz. Si la función devuelve un valor de lista, recibirá el último elemento de la lista. ¿Por qué? Porque Perl te ODIA. :) –

2

Hay dos consideraciones. El más obvio es ¿cuán grande va a ser su matriz? Si se trata de menos de unas pocas docenas de elementos, entonces el tamaño no es un factor (a menos que estés micro-optimizando para alguna función llamada rápidamente, pero tendrías que hacer un poco de perfil de memoria para probarlo primero).

Esa es la parte fácil. La segunda consideración que a menudo se pasa por alto es la interfaz. ¿Cómo se usará la matriz devuelta? Esto es importante porque la desreferenciación de arreglos completos es algo horrible en Perl. Por ejemplo:

for my $info (@{ getInfo($some, $args) }) { 
    ... 
} 

Eso es feo. Esto es mucho mejor.

for my $info (getInfo($some, $args)) { 
    ... 
} 

También se presta para mapeo y grepping.

my @info = grep { ... } getInfo($some, $args); 

Pero volviendo una ref matriz puede ser útil si usted va a seleccionar elementos individuales:

my $address = getInfo($some, $args)->[2]; 

Eso es más simple que:

my $address = (getInfo($some, $args))[2]; 

O:

my @info = getInfo($some, $args); 
my $address = $info[2]; 

Pero en ese punto, usted sho uld pregunta si @info es realmente una lista o un hash.

my $address = getInfo($some, $args)->{address}; 

Lo que no debe hacer es tener getInfo() devolver una referencia array en contexto escalar y una matriz en el contexto de lista. Esto confunde el uso tradicional del contexto escalar como la longitud de la matriz que sorprenderá al usuario.

Finalmente, conectaré mi propio módulo, Method::Signatures, porque ofrece un compromiso para pasar referencias de matriz sin tener que utilizar la sintaxis de matriz de ref.

use Method::Signatures; 

method foo(\@args) { 
    print "@args";  # @args is not a copy 
    push @args, 42; # this alters the caller array 
} 

my @nums = (1,2,3); 
Class->foo(\@nums); # prints 1 2 3 
print "@nums";  # prints 1 2 3 42 

Esto se hace a través de la magia de Data::Alias.otros

+0

Nunca se puede asumir que una función que devuelve una lista en contexto de lista devolverá su longitud en contexto escalar, ya que eso solo ocurre cuando la función devuelve una matriz.Si la función devuelve un valor de lista que no es de matriz, recibirá el último elemento de la lista en lugar de su tamaño. –

+0

¡Entonces no devuelva las listas! Si el mango de su martillo le da astillas, ¡no use guantes, límpielos! Toda la cosa de la "lista contra la matriz" en Perl 5 es una gigantesca trampa para osos justo en el medio del patio de recreo. – Schwern

+0

Estoy totalmente de acuerdo con tu última oración. Me gustaría añadir que la mayoría de los niños en el patio de recreo, y tal vez incluso los diseñadores del patio de recreo, no conocen esta trampa para osos. :) –

0

3 mejoras de rendimiento potencialmente grande si usted está leyendo una totalidad, un archivo bastante grande y cortar en una matriz:

  1. a su vez el uso del búfer con sysread() en lugar de lectura() (manual advierte acerca mezcla)
  2. Pre-ampliar la gama valorando el último elemento - guarda las asignaciones de memoria
  3. uso Desempaquetar() para dividir rápidamente nuevos datos como datos del canal de gráficos uint16_t

Pasar una referencia de matriz a la función permite al programa principal tratar con una matriz simple mientras que la función de escribir escribir una vez y olvidarse utiliza el acceso "$ @" y flecha más complicado -> [$ II] formas. Siendo bastante C'ish, ¡es probable que sea rápido!

Cuestiones relacionadas