計測環境
$ uname -a
Linux kubo39 3.2.0-51-generic-pae #77-Ubuntu SMP Wed Jul 24 20:40:32 UTC 2013 i686 i686 i386 GNU/Linux
$ cat /proc/cpuinfo | grep "model name"
model name : Intel(R) Core(TM) i7-3517U CPU @ 1.90GHz
model name : Intel(R) Core(TM) i7-3517U CPU @ 1.90GHz
model name : Intel(R) Core(TM) i7-3517U CPU @ 1.90GHz
model name : Intel(R) Core(TM) i7-3517U CPU @ 1.90GHz
$ cat /proc/meminfo | grep MemTotal
MemTotal: 4011464 kB
各言語のバージョン
-
python 2.7.3
-
ruby 2.0.0-p247
次の要素を呼び出すコスト
- Pythonのコード
def test_call_next(n=100001):
iter = range(0, n).__iter__()
while True:
try:
iter.next()
except StopIteration:
break
実行結果
$ time python iter.py
real 0m0.042s
user 0m0.028s
sys 0m0.012s
$ time python iter.py
real 0m0.046s
user 0m0.044s
sys 0m0.004s
$ time python iter.py
real 0m0.036s
user 0m0.028s
sys 0m0.004s
- Rubyのコード
def test_call_next n=100000
iter = [*0..n].each
loop do
iter.next
end
end
実行結果
$ time ruby iter.rb
real 0m0.138s
user 0m0.096s
sys 0m0.040s
$ time ruby iter.rb
real 0m0.145s
user 0m0.116s
sys 0m0.028s
$ time ruby iter.rb
real 0m0.147s
user 0m0.124s
sys 0m0.020s
比較
Python約3.5倍ほどはやいですね.ただイテレータの生成コストを考慮に入れているのでよいベンチとはいえないかもですが.
Rubyでsysが大きめなのは
$ strace -c ruby iter.rb
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
98.14 0.006114 0 200005 sigprocmask
1.86 0.000116 1 85 read
0.00 0.000000 0 1 write
0.00 0.000000 0 200 146 open
0.00 0.000000 0 55 close
0.00 0.000000 0 1 execve
0.00 0.000000 0 1 time
0.00 0.000000 0 8 8 access
0.00 0.000000 0 27 brk
0.00 0.000000 0 25 22 ioctl
0.00 0.000000 0 1 gettimeofday
0.00 0.000000 0 7 munmap
0.00 0.000000 0 1 clone
0.00 0.000000 0 1 uname
0.00 0.000000 0 12 mprotect
0.00 0.000000 0 9 _llseek
0.00 0.000000 0 1 mremap
0.00 0.000000 0 16 rt_sigaction
0.00 0.000000 0 23 rt_sigprocmask
0.00 0.000000 0 1 getcwd
0.00 0.000000 0 1 sigaltstack
0.00 0.000000 0 6 getrlimit
0.00 0.000000 0 37 mmap2
0.00 0.000000 0 37 15 stat64
0.00 0.000000 0 96 lstat64
0.00 0.000000 0 117 fstat64
0.00 0.000000 0 14 getuid32
0.00 0.000000 0 14 getgid32
0.00 0.000000 0 15 geteuid32
0.00 0.000000 0 15 getegid32
0.00 0.000000 0 2 getdents64
0.00 0.000000 0 46 fcntl64
0.00 0.000000 0 2 1 futex
0.00 0.000000 0 5 sched_getaffinity
0.00 0.000000 0 1 set_thread_area
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 2 clock_gettime
0.00 0.000000 0 1 openat
0.00 0.000000 0 1 set_robust_list
0.00 0.000000 0 2 pipe2
------ ----------- ----------- --------- --------- ----------------
100.00 0.006230 200895 192 total
sigprocmask(2)
を毎回呼び出しているからのようです.
イテレータ(Enumerator)の生成コスト
- Pythonのコード
def test_create_iterator(n=10001):
[range(0, 1001).__iter__ for _ in xrange(n)]
実行結果
$ time python iter.py
real 0m0.328s
user 0m0.280s
sys 0m0.044s
$ time python iter.py
real 0m0.342s
user 0m0.276s
sys 0m0.064s
$ time python iter.py
real 0m0.324s
user 0m0.268s
sys 0m0.052s
- Rubyのコード
def test_create_enum n=10000
n.times{ [*0..1001].to_enum }
end
実行結果
$ time ruby iter.rb
real 0m0.554s
user 0m0.548s
sys 0m0.004s
$ time ruby iter.rb
real 0m0.558s
user 0m0.552s
sys 0m0.004s
$ time ruby iter.rb
real 0m0.566s
user 0m0.560s
sys 0m0.000s
比較
ここでもPythonのほうが約1.7倍ほどはやいですね.
Pythonのsys timeが大きいのが気になります.
$ strace -c python iter.py
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
53.85 0.000049 0 337 250 open
46.15 0.000042 0 1292 brk
0.00 0.000000 0 183 read
0.00 0.000000 0 89 close
0.00 0.000000 0 1 execve
0.00 0.000000 0 11 11 access
0.00 0.000000 0 5 1 ioctl
0.00 0.000000 0 4 2 readlink
0.00 0.000000 0 55 munmap
0.00 0.000000 0 1 uname
0.00 0.000000 0 11 mprotect
0.00 0.000000 0 3 _llseek
0.00 0.000000 0 68 rt_sigaction
0.00 0.000000 0 1 rt_sigprocmask
0.00 0.000000 0 1 getcwd
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 86 mmap2
0.00 0.000000 0 172 96 stat64
0.00 0.000000 0 9 lstat64
0.00 0.000000 0 141 fstat64
0.00 0.000000 0 1 getuid32
0.00 0.000000 0 1 getgid32
0.00 0.000000 0 1 geteuid32
0.00 0.000000 0 1 getegid32
0.00 0.000000 0 4 getdents64
0.00 0.000000 0 1 1 futex
0.00 0.000000 0 1 set_thread_area
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 2 openat
0.00 0.000000 0 1 set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00 0.000091 2485 361 total
open(2)
と brk(2)
がたくさん時間を食っていますね.とりわけ brk(2)
の呼び出し回数が目を引きます.
ちなみに brk(2)
はプロセスのデータセグメントに割り当てられたメモリ量を変更するためのシステムコールです.
mallocしたときにヒープサイズが足りてない && プロセスが使えるメモリ量に余裕があるときに呼び出されるので、実は
知らないあいだにに使ってる人も多いかと思います.
おまけ
じっさいに必要だったコードを抽象化してみたもので比較
- Pythonのコード
def test_for_generate_enumerator(n=50001):
arr = range(0, 11)
for i in xrange(0, n):
iter = arr.__iter__()
while True:
try:
iter.next()
except StopIteration:
break
実行結果
$ time python iter.py
real 0m0.134s
user 0m0.128s
sys 0m0.004s
$ time python iter.py
real 0m0.134s
user 0m0.128s
sys 0m0.004s
$ time python iter.py
real 0m0.142s
user 0m0.132s
sys 0m0.008s
- Rubyのコード
def test_for_iter_with_generate_enumerator n=50000
arr = [*0..10]
n.times {
iter = arr.to_enum
loop do
iter.next
end
}
end
実行結果
$ time ruby iter.rb
real 0m1.370s
user 0m1.080s
sys 0m0.288s
$ time ruby iter.rb
real 0m1.377s
user 0m0.992s
sys 0m0.380s
$ time ruby iter.rb
real 0m1.362s
user 0m1.060s
sys 0m0.296s
比較
こうもRubyは極端に遅くなるとは...
しかしこのコードでPythonのsys timeがイテレータをたくさん生成したときより小さくなっているのは不思議に感じますね.
結論
どうやらPythonのイテレータのほうが生成・次要素呼び出しともに速いようです.
次回(あるなら)は、実際に処理系のコードを追ってみたいと思います.