map_between を計測してみる

こんにちわ。こんにちわ。今日は ついったー とかいう いんたーねっつ をしていたら危うく童貞認定されるという濡れ衣を着せられて動揺を隠す事を禁じ得ない tell-k です。

さてタイトルの話ですが昨日の記事で、map_between という関数を、いくつか書いた + id:imagawa_yakata さんが書いてくれたヤツがあったので、パフォーマンス的にはどれが良いのかというを計測してみようとおもいます。というのもあまり普段ベンチマークとかとらないなぁ、ちゃんとしなきゃなぁと思ったのでやってみた次第です。

計測対象

昨日までに自分が思いついたのも含めて、4つの下記関数を計測しようと思います。

#!/usr/bin/env python
#-*- coding:utf8 -*-

from itertools import imap

def map_between1(x, func):
    return list(imap(func, x, x[1:]))

def map_between2(lst, func):
    itr = iter(lst)
    x = itr.next()
    for y in itr:
        yield func(x, y)
        x = y

def map_between3(lst, func):
    return [func(lst[i], lst[i + 1]) for i in range(len(lst) - 1)]

def map_between4(lst, func):
    return (func(lst[i], lst[i + 1]) for i in range(len(lst) - 1))

Benchmakerで速度を計測

各関数の計測をするにあたって、Benchmakerモジュールというのがよさげだったのでこれを利用します。

計測のコードはこんな風にしました。Benchmakerいいですね。withをサポートしてて、withブロックで
囲むだけで計測対象になるので楽ですね。

test = range(10000000)
func = lambda x, y: x + y

with Benchmarker() as bm:
    with bm('map_between1'):
        ret = map_between1(test, func)
    with bm('map_between2'):
        ret = list(map_between2(test, func))
    with bm('map_between3'):
        ret = map_between3(test, func)
    with bm('map_between4'):
        ret = list(map_between4(test, func))

計測をするにあたっての留意点です。

  • 最終的に結果として欲しいのはリスト型とします。
  • map_between2 と map_between4 はジェネレータを返すので、list関数でキャストしています。
  • 最初に渡すリストは結果の差異がわかりやすいように巨大にしています。

このコードを実行すると、下記のような結果になります。

## benchmarker:       release 3.0.1 (for python)
## python platform:   darwin [GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)]
## python version:    2.7.2
## python executable: /Users/tell_k/.virtualenvs/snippets/bin/python

##                                user         sys          total        real
map_between1           1.9100    0.1400    2.0500    2.0504
map_between2           3.1900    0.1300    3.3200    3.3222
map_between3           3.9600    0.1900    4.1500    4.1502
map_between4           4.5600    0.1800    4.7400    4.7400

## Ranking                   real
map_between1           2.0504 (100.0%) *************************
map_between2           3.3222 ( 61.7%) ***************
map_between3           4.1502 ( 49.4%) ************
map_between4           4.7400 ( 43.3%) ***********

## Ratio Matrix               real       [01]       [02]       [03]       [04]
[01] map_between1      2.0504  100.0%  162.0%  202.4%  231.2%
[02] map_between2      3.3222    61.7%  100.0%  124.9%  142.7%
[03] map_between3      4.1502    49.4%    80.0%  100.0%  114.2%
[04] map_between4      4.7400    43.3%    70.1%    87.6%  100.0%

map_betweenが 1番速かったようです。3と4はやっぱrangeで作り直してるだけあってダメダメでしたね。

条件を変えて速度を計測

さて上のヤツでは 2 と 4 はせっかくジェネレータが返ってくるのをわざわざリストにキャストしてもったいないですね。

  • 結果はリストでなくても良い

と条件を変更したら、どれだけ変わるんでしょうねというのを計ってみました。コードではlist関数を除去しました。

test = range(10000000)
func = lambda x, y: x + y

with Benchmarker() as bm:
    with bm('map_between1'):
        ret = map_between1(test, func)
    with bm('map_between2'):
        ret = map_between2(test, func)
    with bm('map_between3'):
        ret = map_between3(test, func)
    with bm('map_between4'):
        ret = map_between4(test, func)

結果はこんな感じ。

##                       user       sys     total      real
map_between1           1.9100    0.1300    2.0400    2.0468
map_between2           0.0700    0.0100    0.0800    0.0856
map_between3           3.8800    0.1700    4.0500    4.0492
map_between4           0.1900    0.0300    0.2200    0.2199

## Ranking               real
map_between2           0.0856 (100.0%) *************************
map_between4           0.2199 ( 38.9%) **********
map_between1           2.0468 (  4.2%) *
map_between3           4.0492 (  2.1%) *

## Ratio Matrix          real    [01]    [02]    [03]    [04]
[01] map_between2      0.0856  100.0%  256.7% 2389.9% 4727.9%
[02] map_between4      0.2199   38.9%  100.0%  930.9% 1841.5%
[03] map_between1      2.0468    4.2%   10.7%  100.0%  197.8%
[04] map_between3      4.0492    2.1%    5.4%   50.5%  100.0%

おぉ 2 が1番に躍り出ました!実質的に処理が実行されていないので、当然の結果ですけど。ジェネレータいいですね。

物言い

  • 審判員 「ちょっとまった」
  • 行司 「え?なに?」
  • 審判員 「map_between1 は imapからの戻り値である imapオブジェクトを listでキャストしてるじゃないか。求める結果がリストでなくてもいいならiterableなオブジェクトのimapオブジェクトを返して計測しなきゃ公平とは言えんだろう?八百長かおい?」
  • 行司 「はぁ。。そうですか。。。(八百長て)」

という感じで物言いがついたので map_between1 は imapの戻り値をlist型にキャストしていたので、そのままimapオブジェクトを返すようにして計測しなおしました。

def map_between1(x, func):
    return imap(func, x, x[1:])

以下が結果です。

## benchmarker:       release 3.0.1 (for python)
## python platform:   darwin [GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)]
## python version:    2.7.2
## python executable: /Users/tell_k/.virtualenvs/snippets/bin/python

##                       user       sys     total      real
map_between1           0.0700    0.0200    0.0900    0.0896
map_between2           0.0500    0.0100    0.0600    0.0562
map_between3           4.1200    0.2100    4.3300    4.3233
map_between4           0.1900    0.0400    0.2300    0.2217

## Ranking               real
map_between2           0.0562 (100.0%) *************************
map_between1           0.0896 ( 62.7%) ****************
map_between4           0.2217 ( 25.4%) ******
map_between3           4.3233 (  1.3%) 

## Ratio Matrix          real    [01]    [02]    [03]    [04]
[01] map_between2      0.0562  100.0%  159.4%  394.4% 7692.1%
[02] map_between1      0.0896   62.7%  100.0%  247.4% 4825.4%
[03] map_between4      0.2217   25.4%   40.4%  100.0% 1950.2%
[04] map_between3      4.3233    1.3%    2.1%    5.1%  100.0%

結果はこんな感じでした。list型にキャストしなければimapもいい感じで速度は出るようですね。

  • 行司「満足した?」
  • 審判員「ち」
  • 行司 「(ちってなんだよ。ちって。)」

まとめ

  • 今回は MacOSX(Lion) の MBA (CPU 1.8 GHz, Memory 4GB)で計測を行いました。Pythonのバージョンは 2.7.2です
  • 巨大なリストを扱うときは要件に応じて iter や ジェネレータの利用を検討するといいでしょう。
  • 今回は処理速度をメインに計測をしたので、メモリ消費量とはまた別の話です。
  • 速度差があるように見えますが、差が分かりやすいように巨大なデータを扱っているので注意しましょう。
  • Benchmaker は 簡単に使えていいですね。
  • 他に良い計測方法とかあったら教えてください >

メモリ消費量も比較してみたんだけど、今日は眠いのでここまで