LoginSignup
5
5

More than 5 years have passed since last update.

PythonのiteratorとRubyのEnumeratorを比較してみた

Posted at

計測環境

$ 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のイテレータのほうが生成・次要素呼び出しともに速いようです.

次回(あるなら)は、実際に処理系のコードを追ってみたいと思います.

5
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5