我想创建一个大型测试文件,其中包含按秒列出的日期行,但我的方法花费了非常长的时间...(或者至少,感觉就是这样:) ... 43 分钟只创建了 1051201 行。 20.1 MB 文件....
我想创建一个更大的文件,每行的日期都是唯一的。
有没有比我处理它的方法更快的方法?:
# # 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 date 有一个批处理选项:在标准输入上输入日期,然后它会漂亮地打印它们。此外,要枚举整数范围,请使用seq
,它可能比解释 shell 中的循环更快。
seq -f @%12.0f $secBeg $secEnd | date -f - '+%Y-%m-%d %H:%M:%S' >"$file"
cnt=$(($secY2 + 1))
一般来说,如果您的 shell 脚本太慢,请尝试在专用实用程序中执行内部循环 - 这里是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
(这个版本只打印 4 小时,而不是 2 年。)
为了更好地了解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
fork 并重复调用外部命令。
如果我们将 移到循环>>
的末尾while
,它几乎不会产生任何影响。
$ time ./junk2.sh
...
./junk2.sh 2.46s user 3.18s system 22% cpu 25.659 total
但吉尔斯的答案找到了一种只能跑步的方法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
和4clones
与原版中的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
这也很多,很多比原始脚本快,但比吉尔斯的稍慢。
答案3
该脚本在我手边的虚拟机上用 7m50.0s 生成了 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