Ich habe Pythons Iterator und Rubys Enumerator verglichen

Messumgebung

$ 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

Version jeder Sprache

Kosten für den Aufruf des nächsten Elements

def test_call_next(n=100001):
    iter = range(0, n).__iter__()
    while True:
        try:
            iter.next()
        except StopIteration:
            break

Ausführungsergebnis

$ 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
def test_call_next n=100000
  iter = [*0..n].each
  loop do
    iter.next
  end
end

Ausführungsergebnis

$ 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

Vergleich

Es ist ungefähr 3,5-mal schneller als Python, aber es ist möglicherweise keine gute Bank, da es die Kosten für die Generierung von Iteratoren berücksichtigt.

Der Grund, warum sys in Ruby groß ist

$ 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

Es scheint, dass es daran liegt, dass jedes Mal "sigprocmask (2)" aufgerufen wird.

Erzeugungskosten für Iterator (Enumerator)

def test_create_iterator(n=10001):
    [range(0, 1001).__iter__ for _ in xrange(n)]

Ausführungsergebnis

$ 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
def test_create_enum n=10000
  n.times{ [*0..1001].to_enum }
end

Ausführungsergebnis

$ 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

Vergleich

Auch hier ist Python etwa 1,7-mal schneller.

Ich mache mir Sorgen, dass die Systemzeit von Python groß ist.

$ 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)" und "brk (2)" nehmen viel Zeit in Anspruch, insbesondere die Anzahl der Anrufe bei "brk (2)" ist ein Blickfang.

Übrigens ist "brk (2)" ein Systemaufruf zum Ändern der dem Datensegment eines Prozesses zugewiesenen Speichermenge.

Die Heap-Größe reicht beim Mallocing nicht aus && Der Prozess wird aufgerufen, wenn genügend Speicher verfügbar ist, also ist es tatsächlich so Ich denke, viele Leute benutzen es, ohne es zu wissen.

Bonus

Vergleichen Sie mit einer Abstraktion des Codes, der wirklich benötigt wurde

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

Ausführungsergebnis

$ 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
def test_for_iter_with_generate_enumerator n=50000
  arr = [*0..10]
  n.times {
    iter = arr.to_enum
    loop do
      iter.next
    end
  }
end

Ausführungsergebnis

$ 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

Vergleich

Ruby ist so extrem langsam ...

Es ist jedoch seltsam, dass Pythons Systemzeit in diesem Code kürzer ist als bei der Generierung vieler Iteratoren.

Fazit

Anscheinend ist der Python-Iterator sowohl für die Generierung als auch für den nächsten Elementaufruf schneller.

Beim nächsten Mal (falls vorhanden) möchte ich dem Verarbeitungscode tatsächlich folgen.

Recommended Posts

Ich habe Pythons Iterator und Rubys Enumerator verglichen
Ich habe Java und Python verglichen!
Ich habe Klinge und Jinja2 verglichen
Ich habe Qiskit und Blueqat (Anfänger) verglichen.
Ich habe Java und Ruby persönlich verglichen
Ich habe "Python Dictionary Type" und "Excel Function" verglichen.
Ich mag die Einschlussnotation von Python, also habe ich sie mit der Karte verglichen
Ich habe Python more-itertools 2.5 → 2.6 verglichen