Perl 서브루틴에서 전체 배열을 반환하는 것이 비효율적입니까?
-
23-08-2019 - |
문제
나는 종종 Perl에서 어떤 정보로 배열을 채우는 서브루틴을 가지고 있습니다.나는 또한 C++ 해킹에 익숙하기 때문에 Perl에서 참조를 사용하여 다음과 같은 작업을 수행하는 경우가 많습니다.
my @array;
getInfo(\@array);
sub getInfo {
my ($arrayRef) = @_;
push @$arrayRef, "obama";
# ...
}
보다 간단한 버전 대신:
my @array = getInfo();
sub getInfo {
my @array;
push @array, "obama";
# ...
return @array;
}
물론 그 이유는 배열이 서브루틴에서 로컬로 생성된 다음 반환 시 복사되는 것을 원하지 않기 때문입니다.
그렇죠?아니면 Perl은 어쨌든 그것을 최적화합니까?
해결책
처음에 배열 참조를 반환하는 것은 어떻습니까?
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()};
Dehmann의 의견에 따라 편집 :
함수에서 일반 배열을 사용하고 참조를 반환 할 수도 있습니다.
sub getInfo {
my @array;
push @array, 'foo';
# ...
return \@array;
}
다른 팁
통과 참조가 더 효율적이지만 차이는 C ++만큼 크지 않습니다. 인수 값 자체 (즉, 배열의 값)는 항상 참조로 전달됩니다 (반환 된 값은 복사됩니다).
질문은 : 중요합니까? 대부분의 경우에는 그렇지 않습니다. 5 개의 요소를 반환하고 있다면 귀찮게하지 마십시오. 100'000 요소를 반환/통과하는 경우 참조를 사용하십시오. 병목 현상 인 경우에만 최적화하십시오.
최종 반추에 응답하기 위해 Perl은 이것을 최적화하지 않습니다. 배열을 반환하고 스칼라를 반환하는 것은 근본적으로 다르기 때문에 실제로는 할 수 없습니다.
많은 양의 데이터를 다루거나 성능이 큰 문제인 경우 C 습관이 잘 제공됩니다. 구조 자체가 아닌 데이터 구조에 대한 참조를 통과하고 반환하여 복사 할 필요가 없습니다. 그러나 Leon Timmermans가 지적했듯이, 대부분의 시간은 소수의 데이터와 성능을 다루는 것이 그다지 큰 거래가 아니므로 가장 읽을 수있는 방법으로는 그다지 중요하지 않습니다.
이것이 제가 일반적으로 배열을 반환하는 방식입니다.
sub getInfo {
my @array;
push @array, 'foo';
# ...
return @array if wantarray;
return \@array;
}
이렇게하면 원하는 방식으로, 스칼라 또는 목록 컨텍스트에서 작동합니다.
my $array = getInfo;
my @array = getInfo;
$array->[0] == $array[0];
# same length
@$array == @array;
코드의 느린 부분이라는 것을 알지 않는 한 최적화하려고하지 않을 것입니다. 그럼에도 불구하고 나는 벤치 마크를 사용하여 실제로 어떤 서브 루틴이 실제로 더 빠른지 확인할 것입니다.
두 가지 고려 사항이 있습니다. 명백한 것은 배열이 얼마나 큰지? 수십 개의 요소 미만인 경우 크기는 요인이 아닙니다 (빠르게 호출되는 기능을 미세 최적화하지 않는 한 먼저 메모리 프로파일 링을 수행해야합니다).
그것은 쉬운 부분입니다. 간과 된 두 번째 고려 사항은 인터페이스입니다. 반환 된 배열은 어떻게 사용됩니까? 전체 배열 Dereferencing이 Perl에서 끔찍하기 때문에 이것은 중요합니다. 예를 들어:
for my $info (@{ getInfo($some, $args) }) {
...
}
못 생겼어. 이것은 훨씬 낫습니다.
for my $info ( getInfo($some, $args) ) {
...
}
또한 매핑과 그레핑에 적합합니다.
my @info = grep { ... } getInfo($some, $args);
그러나 개별 요소를 선택하려면 배열 심판을 반환하는 것이 유용 할 수 있습니다.
my $address = getInfo($some, $args)->[2];
그것은 :보다 간단합니다.
my $address = (getInfo($some, $args))[2];
또는:
my @info = getInfo($some, $args);
my $address = $info[2];
그러나 그 시점에서 @info가 진정으로 목록인지 해시인지 의문을 제기해야합니다.
my $address = getInfo($some, $args)->{address};
당신이하지 말아야 할 것은 가지고 있습니다 getInfo()
스칼라 컨텍스트에서 배열 심판을 반환하고 목록 컨텍스트에서 배열을 반환합니다. 이것은 스칼라 컨텍스트의 전통적인 사용을 배열 길이로 사용하여 사용자를 놀라게합니다.
마지막으로, 나는 내 자신의 모듈을 꽂을 것입니다. 방법 :: 서명, 배열 참조 구문을 사용하지 않고 배열 참조를 전달하기위한 타협을 제공하기 때문입니다.
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
이것은 마법을 통해 이루어집니다 데이터 :: 별명.
전체 용량이 큰 파일을 읽고 이를 배열로 분할하는 경우 잠재적으로 큰 성능 향상이 가능한 3가지:
- read () 대신 sysread ()로 버퍼링을 끄십시오 (믹싱에 대한 수동 경고)
- 마지막 요소를 평가하여 배열을 사전 확장 - 메모리 할당을 저장합니다.
- Unpack()을 사용하여 uint16_t 그래픽 채널 데이터와 같은 데이터를 신속하게 분할합니다.
함수에 배열 참조를 전달하면 기본 프로그램이 간단한 배열을 처리할 수 있는 반면, 한 번 쓰고 잊어버리는 작업자 함수는 더 복잡한 "$@" 및 화살표 ->[$II] 액세스 형식을 사용합니다.꽤 C'ish이기 때문에 빠를 것 같습니다!
나는 Perl에 대해 아무것도 모른다. 그래서 이것은 언어 중립 대답이다.
어떤 의미에서, 서브 루틴에서 배열을 호출 프로그램으로 복사하는 것은 비효율적입니다. 비 효율성은 사용 된 추가 메모리와 한 장소에서 다른 장소로 데이터를 복사하는 데 걸리는 시간에 발생합니다. 반면에, 가장 큰 배열을 제외한 모든 배열을 제외한 모든 배열을 위해, 당신은 망할 수 없으며, 우아함, 욕심 또는 다른 이유를 위해 배열을 복사하는 것을 선호 할 수 있습니다.
효율적인 솔루션은 서브 루틴이 호출 프로그램을 배열의 주소를 통과시키는 것입니다. 내가 말했듯이, 나는이 점에서 Perl의 기본 행동에 대한 단서가 없었다. 그러나 일부 언어는 프로그래머에게 어떤 접근법을 선택할 수있는 옵션을 제공합니다.