초 단위로 나열된 날짜 행이 있는 큰 테스트 파일을 만들고 싶지만 내 접근 방식은 매우 오랜 시간이 걸립니다... (또는 적어도 그 느낌입니다 :) ... 1051201 행만 만드는 데 43분이 걸립니다. 20.1MB 파일....
각 행의 날짜가 고유한 더 큰 파일을 만들고 싶습니다.
제가 처리하는 방식보다 더 빠른 방법이 있나요? :
# # BEGIN CREATE TEST DATA ============
# # Create some dummy data.
file=/tmp/$USER/junk
((secY2 =s3600*24*365*2))
cnt=0
secBeg=$(date --date="2010-01-01 00:00:00" +%s)
secEnd=$((secBeg+secY2))
((sec=secBeg))
while ((sec<=secEnd)) ; do
date -d '1970-01-01 UTC '$sec' seconds' '+%Y-%m-%d %H:%M:%S' >>"$file"
((sec+=1))
((cnt+=1))
done
ls -l "$file"
echo Lines written: $cnt
# END CREATE TEST DATA ============
답변1
아직 벤치마킹을 수행하지는 않았지만 몇 가지 잠재적인 개선이 있을 것으로 보입니다.
호출할 때마다 파일을 열고 닫습니다 date
. 이것은 낭비입니다. 전체 루프에 리디렉션을 넣으십시오.
while …; do …; done >"$file"
date
각 회선에 별도의 전화를 걸고 있습니다. 유닉스는 외부 프로그램을 빠르게 호출하는 데 능숙하지만 내부 프로그램은 여전히 더 좋습니다. GNU 날짜에는 배치 옵션이 있습니다. 표준 입력에 날짜를 입력하면 날짜가 예쁘게 인쇄됩니다. 또한 정수 범위를 열거하려면 를 사용하세요. seq
이는 셸에서 루프를 해석하는 것보다 더 빠를 수 있습니다.
seq -f @%12.0f $secBeg $secEnd | date -f - '+%Y-%m-%d %H:%M:%S' >"$file"
cnt=$(($secY2 + 1))
일반적으로 쉘 스크립트가 너무 느린 경우 전용 유틸리티에서 내부 루프를 실행해 보십시오. 여기서는 seq
및 date
이지만 일반적으로 sed
또는 입니다 awk
. 그렇게 할 수 없다면 Perl이나 Python과 같은 더 높은 수준의 스크립팅 언어로 전환하십시오(그러나 사용 사례에 적합하면 전용 유틸리티가 더 빠른 경우가 많습니다).
답변2
우리는 그것이 느리게 실행된다는 것을 알고 있습니다:
$ time ./junk.sh
Lines written: 14401
./junk.sh 2.27s user 3.31s system 21% cpu 25.798 total
(이 버전은 2년이 아닌 4시간만 인쇄합니다.)
시간이 어디에 소비되는지 더 잘 이해하기 위해 bash
를 사용할 수 있습니다 strace -c
.
$ strace -c ./junk.sh
Lines written: 14401
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
79.01 0.128906 4 28806 14403 waitpid
17.92 0.029241 2 14403 clone
2.45 0.003999 0 158448 rt_sigprocmask
0.33 0.000532 0 28815 rt_sigaction
0.29 0.000479 0 14403 sigreturn
따라서 우리는 상위 두 호출이 waitpid
및 임을 알 수 있습니다 clone
. 그 자체로는 많은 시간을 차지하지 않지만(단지 0.128906초와 0.029241초) 많이 호출되는 것을 볼 수 있으므로 문제는 date
각 숫자를 에코하기 위해 별도의 명령을 실행해야 한다는 점이라고 의심됩니다.
bash
그런 다음 몇 가지 검색을 수행한 결과 다음을 통해 gprof
지원되도록 컴파일 할 수 있음을 발견했습니다 .
$ ./configure --enable-profiling --without-bash-malloc
$ make
이제 사용해 보세요:
$ ./bash-gprof junk.sh
Lines written: 14401
$ gprof ./bash-gprof gmon.out
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
8.05 0.28 0.28 14403 0.00 0.00 make_child
6.61 0.51 0.23 __gconv_transform_utf8_internal
5.75 0.71 0.20 fork
5.75 0.91 0.20 259446 0.00 0.00 hash_search
5.17 1.09 0.18 129646 0.00 0.00 dispose_words
bash
따라서 함수 이름이 의미가 있다고 가정하면 외부 명령을 반복적으로 분기하고 호출하는 것이 문제라는 것을 확인할 수 있습니다 .
>>
루프 의 끝 으로 이동하면 while
거의 효과가 없습니다.
$ time ./junk2.sh
...
./junk2.sh 2.46s user 3.18s system 22% cpu 25.659 total
그러나 Giles의 답변은 run 만 실행하는 방법을 찾았 date
으며 놀랍게도 그것은많은서둘러요:
$ time ./bash-gprof junk3.sh
Lines written: 14401
./bash-gprof junk3.sh 0.10s user 0.16s system 96% cpu 0.264 total
$ strace -c ./bash-gprof junk3.sh
Lines written: 14401
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
97.63 0.039538 5648 7 3 waitpid
2.37 0.000961 37 26 writev
0.00 0.000000 0 9 read
...
0.00 0.000000 0 4 clone
$ gprof ./bash-gprof gmon.out
Flat profile:
Each sample counts as 0.01 seconds.
no time accumulated
% cumulative self self total
time seconds seconds calls Ts/call Ts/call name
0.00 0.00 0.00 1162 0.00 0.00 xmalloc
0.00 0.00 0.00 782 0.00 0.00 mbschr
0.00 0.00 0.00 373 0.00 0.00 shell_getc
7 waitpids
과 4를 clones
원본의 28806과 14403과 비교해보세요!
따라서 교훈은 다음과 같습니다. 여러 번 반복되는 루프 내에서 외부 명령을 호출해야 하는 경우 이를 루프 밖으로 이동하는 방법을 찾거나 호출 프로그래밍 언어가 필요하지 않은 외부 명령으로 전환해야 합니다. 작업을 수행합니다.
요청에 따라 Iain의 방법을 기반으로 한 테스트(동일한 변수 이름과 루프를 사용하도록 수정됨):
#!/bin/bash
datein=junk.$$.datein
file=junk.$$
((secY2=3600*4))
cnt=0
secBeg=$(date --date="2010-01-01 00:00:00" +%s)
secEnd=$((secBeg+secY2))
((sec=secBeg))
while ((sec<=secEnd)) ; do
echo @$sec >>"$datein"
((sec+=1))
((cnt+=1))
done
date --file="$datein" '+%Y-%m-%d %H:%M:%S' >>"$file"
ls -l "$file"
rm "$datein"
echo Lines written: $cnt
결과:
$ time ./bash-gprof ./junk4.sh
Lines written: 14401
./bash-gprof ./junk4.sh 0.92s user 0.20s system 94% cpu 1.182 total
$ strace -c ./junk4.sh
Lines written: 14401
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
91.71 0.116007 14501 8 4 waitpid
3.70 0.004684 0 14402 write
1.54 0.001944 0 28813 close
1.35 0.001707 0 72008 1 fcntl64
0.88 0.001109 0 43253 rt_sigprocmask
0.45 0.000566 0 28803 dup2
0.36 0.000452 0 14410 open
$ gprof ./bash-gprof gmon.out
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
22.06 0.15 0.15 __gconv_transform_utf8_internal
16.18 0.26 0.11 mbrtowc
7.35 0.31 0.05 _int_malloc
5.88 0.35 0.04 __profile_frequency
4.41 0.38 0.03 345659 0.00 0.00 readtok
4.41 0.41 0.03 _int_free
2.94 0.43 0.02 230661 0.00 0.00 hash_search
2.94 0.45 0.02 28809 0.00 0.00 stupidly_hack_special_variables
1.47 0.46 0.01 187241 0.00 0.00 cprintf
1.47 0.47 0.01 115232 0.00 0.00 do_redirections
그래서 나타나고 있습니다 close
.open
>>
Eelvex는 이제 각 라인과 루프 >
주변의 관찰 에 영향을 미치기 시작합니다 while
.
그 점을 고려해보자...
#!/bin/bash
datein=junk.$$.datein
file=junk.$$
((secY2=3600*4))
cnt=0
secBeg=$(date --date="2010-01-01 00:00:00" +%s)
secEnd=$((secBeg+secY2))
for ((sec=secBeg; sec<=secEnd; sec=sec+1)) ; do
echo @$sec
((cnt+=1))
done >"$datein"
date --file="$datein" '+%Y-%m-%d %H:%M:%S' >>"$file"
ls -l "$file"
rm "$datein"
echo Lines written: $cnt
$ time ./junk6.sh
Lines written: 14401
./junk6.sh 0.58s user 0.14s system 95% cpu 0.747 total
$ strace -c junk6.sh
Lines written: 14401
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
97.41 0.092263 11533 8 4 waitpid
2.06 0.001949 0 43252 rt_sigprocmask
0.53 0.000506 0 14402 write
0.00 0.000000 0 13 read
0.00 0.000000 0 10 open
0.00 0.000000 0 13 close
0.00 0.000000 0 1 execve
$ gprof ./bash-gprof gmon.out
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
10.00 0.05 0.05 72025 0.00 0.00 expand_word_internal
10.00 0.10 0.05 __gconv_transform_utf8_internal
8.00 0.14 0.04 __profile_frequency
8.00 0.18 0.04 _int_malloc
4.00 0.20 0.02 1355024 0.00 0.00 xmalloc
4.00 0.22 0.02 303217 0.00 0.00 mbschr
이것도 많이,많은원본 스크립트보다 빠르지만 Giles의 스크립트보다 약간 느립니다.
답변3
이 스크립트는 제가 가지고 있는 가상 머신에서 7분 50초 만에 천만 줄의 201Mb 파일을 생성했습니다. 약 1.5Gb/시간.
#!/bin/bash
Tstart=$(date +%s)
let Tend=$Tstart+100000000
[ -e datein.txt ] && rm datein.txt
[ -e logfile.log ] && rm logfile.log
for (( Tloop=Tstart; Tloop <=Tend; Tloop++ ))
do
echo @$Tloop >> datein.txt
done
date --file=datein.txt '+%Y-%m-%d %H:%M:%S' >>logfile.log