秒単位でリストされている日付行を持つ大きなテストファイルを作成したいのですが、私のアプローチは非常に長い時間がかかります...(または少なくともその感じです:)... 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
各回線に別々の電話をかけています。 Unixは外部プログラムをすばやく呼び出すのに精通していますが、内部プログラムは依然として優れています。 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
したがって、我々は上位2つの呼び出しがあることを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の答えはランだけを実行する方法を見つけ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
7waitpids
と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秒で1000万行の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