배쉬 5.0 해결됨
배경
배경 지식(그리고 이 질문에 대한 반대표를 피하기 위해)을 위해 이 문제를 해결하게 된 경로를 설명하겠습니다(두 달 후에 제가 기억할 수 있는 최선의 방법입니다).
유니코드 문자 목록에 대해 몇 가지 쉘 테스트를 수행한다고 가정해 보겠습니다.
printf "$(printf '\\U%x ' {33..200})"
그리고 약 100만 개 이상의 유니코드 문자가 있으며 그 중 20,000개를 테스트하는 것은 많은 것처럼 보이지 않습니다.
또한 문자를 위치 인수로 설정한다고 가정합니다.
set -- $(printf "$(printf '\\U%x ' {33..20000})")
목적은 문자를 각 함수에 전달하여 다르게 처리하는 것입니다. 따라서 함수는 다음과 같은 형태 test1 "$@"
또는 유사한 형태를 가져야 합니다. 이제 나는 이것이 bash에서 얼마나 나쁜 생각인지 깨달았습니다.
이제 어느 솔루션이 더 나은지 알아보기 위해 각 솔루션(n=1000)의 시간을 측정해야 한다고 가정해 보겠습니다. 이 경우 다음과 유사한 구조를 얻게 됩니다.
#!/bin/bash --
TIMEFORMAT='real: %R' # '%R %U %S'
set -- $(printf "$(printf '\\U%x ' {33..20000})")
n=1000
test1(){ echo "$1"; } >/dev/null
test2(){ echo "$#"; } >/dev/null
test3(){ :; }
main1(){ time for i in $(seq $n); do test1 "$@"; done
time for i in $(seq $n); do test2 "$@"; done
time for i in $(seq $n); do test3 "$@"; done
}
main1 "$@"
이러한 기능은 test#
매우 간단하며 여기에만 표시됩니다.
엄청난 지연의 원인을 찾기 위해 원래 기능이 점차 제거되었습니다.
위의 스크립트는 작동하므로 실행하고 아주 작은 작업을 수행하는 데 시간을 낭비할 수 있습니다.
대기 시간이 정확히 어디인지 찾기 위해 단순화하는 과정에서(많은 시도를 거친 후 각 테스트 기능을 거의 아무것도 없는 상태로 줄이는 것은 극단적임) 시간이 얼마나 개선되었는지 확인하기 위해 각 테스트 기능에 전달 매개변수를 제거하기로 결정했습니다. 별로.
직접 시도해 보려면 "$@"
함수의 모든 항목을 제거 main1
하거나 복사본을 만들고 다시 테스트(또는 둘 다 main1
및 복사본 main2
( 사용 main2 "$@"
))하여 비교하세요. 이는 원본 게시물(OP)의 기본 구조입니다.
하지만 저는 알고 싶습니다. 쉘이 "아무 것도 하지 않는" 데 왜 그렇게 오랜 시간이 걸리는 걸까요? 예, 단지 "몇 초"만 소요됩니다. 그런데 왜 그렇습니까?
이로 인해 다른 쉘에서 테스트하게 되었고 bash에만 이 문제가 있음을 발견했습니다.
시도해 보세요 ksh ./script
(위와 동일한 스크립트).
test#
이는 매개변수 없이 함수를 호출하는 경우( ) 상위 함수( )의 매개변수에 의해 지연된다는 설명으로 이어집니다 . main#
아래 설명은 다음과 같습니다. 아래 원본 게시물(OP)도 마찬가지입니다.
원본 게시물.
아무것도 하지 않는 함수(Bash 4.4.12(1)-릴리스에서)를 호출하는 것은 호출하는 것 보다 f1(){ :; }
1000배 느립니다.:
오직정의된 매개변수가 있는 경우부모함수를 호출하는 이유는 무엇입니까?
#!/bin/bash
TIMEFORMAT='real: %R'
f1 () { :; }
f2 () {
echo " args = $#";
printf '1 function no args yes '; time for ((i=1;i<$n;i++)); do : ; done
printf '2 function yes args yes '; time for ((i=1;i<$n;i++)); do f1 ; done
set --
printf '3 function yes args no '; time for ((i=1;i<$n;i++)); do f1 ; done
echo
}
main1() { set -- $(seq $m)
f2 ""
f2 "$@"
}
n=1000; m=20000; main1
결과 test1
:
args = 1
1 function no args yes real: 0.013
2 function yes args yes real: 0.024
3 function yes args no real: 0.020
args = 20000
1 function no args yes real: 0.010
2 function yes args yes real: 20.326
3 function yes args no real: 0.019
함수에는 매개변수가 사용되지 않고, 입력이나 출력도 사용되지 않으며 f1
, 1000배의 지연은 예상치 못한 일입니다.1
테스트를 여러 셸로 확장하면 대부분의 셸에서 문제나 지연 없이 결과가 일관됩니다(동일한 n 및 m 사용).
test2(){
for sh in dash mksh ksh zsh bash b50sh
do
echo "$sh" >&2
# \time -f '\t%E' seq "$m" >/dev/null
# \time -f '\t%E' "$sh" -c 'set -- $(seq '"$m"'); for i do :; done'
\time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do : ; done;' $(seq $m)
\time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do f ; done;' $(seq $m)
done
}
test2
결과:
dash
0:00.01
0:00.01
mksh
0:00.01
0:00.02
ksh
0:00.01
0:00.02
zsh
0:00.02
0:00.04
bash
0:10.71
0:30.03
b55sh # --without-bash-malloc
0:00.04
0:17.11
b56sh # RELSTATUS=release
0:00.03
0:15.47
b50sh # Debug enabled (RELSTATUS=alpha)
0:04.62
xxxxxxx More than a day ......
seq
매개변수 목록이나 매개변수 목록 처리가 지연의 원인이 아닌지 확인하려면 다른 두 테스트의 주석 처리를 제거하세요 .
답변1
복사한 곳:루프에 지연이 발생하는 이유는 무엇입니까?귀하의 요청에 따르면:
테스트 사례를 다음과 같이 단축할 수 있습니다.
time bash -c 'f(){ :;};for i do f; done' {0..10000}
함수를 호출하고 있으며 $@
이를 트리거하는 것 같습니다.
$@
내 생각에는 스택에 저장한 다음 복원하는 데 시간이 소요되는 것 같습니다 . bash
모든 값을 복사하거나 그런 식으로 하면 매우 비효율적일 수 있습니다. 시간은 O(n²)인 것 같습니다.
다른 쉘에서도 동일한 시간을 얻을 수 있습니다.
time zsh -c 'f(){ :;};for i do f "$@"; done' {0..10000}
이것은 인수 목록을 함수에 전달하는 곳입니다. 이번에는 셸입니다.필요값을 복사합니다( bash
결국 5배 느려짐).
(처음에는 bash 5(현재 알파)에서 더 나쁘다고 생각했지만 @egmont가 지적했듯이 개발 빌드에서 malloc 디버깅을 활성화하는지 여부에 따라 달라집니다. bash
예를 들어 Ubuntu에서 빌드를 사용하려는 경우 --without-bash-malloc
)