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

## 計測環境

```
$ 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のコード

```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のコード

```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のコード

```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のコード

```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のコード

```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のコード

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

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