다음 테스트를 수행했으며 시스템에서 두 번째 스크립트의 결과 차이가 약 100 배 더 깁니다.
내 파일은 strace 출력입니다 bigfile
$ wc -l bigfile.log
1617000 bigfile.log
스크립트
xtian@clafujiu:~/tmp$ cat p1.sh
tail -n 1000000 bigfile.log | grep '"success": true' | wc -l
tail -n 1000000 bigfile.log | grep '"success": false' | wc -l
xtian@clafujiu:~/tmp$ cat p2.sh
log=$(tail -n 1000000 bigfile.log)
echo "$log" | grep '"success": true' | wc -l
echo "$log" | grep '"success": true' | wc -l
나는 실제로 grep과 일치하는 것이 없으므로 마지막 파이프에 아무것도 기록되지 않습니다. wc -l
타이밍은 다음과 같습니다.
xtian@clafujiu:~/tmp$ time bash p1.sh
0
0
real 0m0.381s
user 0m0.248s
sys 0m0.280s
xtian@clafujiu:~/tmp$ time bash p2.sh
0
0
real 0m46.060s
user 0m43.903s
sys 0m2.176s
그래서 strace 명령을 통해 두 스크립트를 다시 실행했습니다.
strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh
추적 결과는 다음과 같습니다.
$ cat p1.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
97.24 0.508109 63514 8 2 waitpid
1.61 0.008388 0 84569 read
1.08 0.005659 0 42448 write
0.06 0.000328 0 21233 _llseek
0.00 0.000024 0 204 146 stat64
0.00 0.000017 0 137 fstat64
0.00 0.000000 0 283 149 open
0.00 0.000000 0 180 8 close
...
0.00 0.000000 0 162 mmap2
0.00 0.000000 0 29 getuid32
0.00 0.000000 0 29 getgid32
0.00 0.000000 0 29 geteuid32
0.00 0.000000 0 29 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 7 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 0.522525 149618 332 total
그리고 p2.strace
$ cat p2.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
75.27 1.336886 133689 10 3 waitpid
13.36 0.237266 11 21231 write
4.65 0.082527 1115 74 brk
2.48 0.044000 7333 6 execve
2.31 0.040998 5857 7 clone
1.91 0.033965 0 705681 read
0.02 0.000376 0 10619 _llseek
0.00 0.000000 0 248 132 open
...
0.00 0.000000 0 141 mmap2
0.00 0.000000 0 176 126 stat64
0.00 0.000000 0 118 fstat64
0.00 0.000000 0 25 getuid32
0.00 0.000000 0 25 getgid32
0.00 0.000000 0 25 geteuid32
0.00 0.000000 0 25 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 6 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 1.776018 738827 293 total
분석
놀랍게도, 두 경우 모두 대부분의 시간이 프로세스가 완료되기를 기다리는 데 소비되지만 p2는 p1보다 2.63 배 더 길며, 다른 사람들이 언급했듯이 p2.sh에서 늦게 시작하고 있습니다.
이제를 잊고 열을 waitpid
무시하고 %
두 트레이스에서 초 열을 확인하십시오.
가장 큰 시간 p1은 읽을 파일이 많기 때문에 대부분의 시간을 읽기에 소비하지만 아마도 p2는 p1보다 28.82 배 더 오래 읽습니다. - bash
큰 파일을 변수로 읽을 것으로 예상하지 않고 한 번에 버퍼를 읽고 행으로 분할 한 다음 다른 파일을 가져 오는 것입니다.
읽기 카운트 p2는 705k 대 p1의 경우 84k이며, 각 읽기는 컨텍스트를 커널 공간으로 전환하고 다시 출력해야합니다. 읽기 및 컨텍스트 전환 횟수의 거의 10 배
쓰기 시간 p2는 p1보다 쓰기 시간이 41.93 배 더 길다
쓰기 카운트 p1은 p2, 42k 대 21k보다 많은 쓰기를 수행하지만 훨씬 빠릅니다.
아마도 테일 쓰기 버퍼와 반대로 echo
라인이 있기 때문일 것입니다 grep
.
또한 p2는 읽기보다 쓰기에 더 많은 시간을 소비하며 p1은 다른 방식으로 진행됩니다!
기타 요소brk
시스템 호출 수를 살펴보십시오 . p2는 읽는 것보다 2.42 배 더 긴 시간을 소비합니다! p1에서 (등록조차하지 않음). brk
프로그램이 처음에 충분히 할당되지 않았기 때문에 프로그램이 주소 공간을 확장해야 할 때, 아마도 bash가 해당 파일을 변수로 읽어야하고 파일이 너무 클 것으로 기대하지 않기 때문에 아마도 @scai가 언급 한 것처럼 파일이 너무 커져도 작동하지 않습니다.
tail
이것은 아마도 매우 효율적인 파일 판독기 일 것입니다. 이것은 이것이 의도 한 것이므로 파일을 압축하고 줄 바꿈을 스캔하여 커널이 i / o를 최적화 할 수있게합니다. bash는 읽고 쓰는 데 시간이 오래 걸리지 않습니다.
P2는 44ms와의 41ms를 보낸다 clone
과 execv
는 P1에 대한 측정 가능한 양이 아니다. 아마도 꼬리에서 변수를 읽고 작성하는 것 같습니다.
마지막으로 총계 p1은 ~ 150k 시스템 호출을 실행하고 p2 740k (4.93 배 더 큼)를 실행합니다.
waitpid를 제거하면 p1은 시스템 호출을 실행하는 데 0.014416 초, p2 0.439132 초 (30 배 이상)를 소비합니다.
따라서 p2는 시스템 호출이 완료되고 커널이 메모리를 재구성하기를 기다리는 것을 제외하고는 사용자 공간에서 대부분의 시간을 소비하는 것으로 보이며, p1은 더 많은 쓰기를 수행하지만보다 효율적이며 시스템로드를 크게 줄이므로 더 빠릅니다.
결론
bash 스크립트를 작성할 때 메모리를 통한 코딩에 대해 결코 걱정하지 않으려 고합니다. 효율적이지 않다고 말하는 것은 아닙니다.
tail
memory maps
커널은 i / o를 읽고 효율적으로 읽을 수 있도록 파일 을 수행하도록 설계되었습니다 .
문제를 최적화하는 더 좋은 방법은 먼저 grep
' "성공":'행을 찾은 다음 true와 false를 계산하고 grep
count 옵션을 사용 wc -l
하여 꼬리를 피하고 더 나은 방법으로 꼬리를 통과시켜 awk
true와 count를 계산하는 것입니다. 동시에 거짓. p2는 메모리가 brks와 뒤섞이는 동안 시간이 오래 걸리고 시스템에로드를 추가합니다.