Python
高速化

Python のmapに複数引数の関数を用いる場合について

目的

pythonでリストに対して関数操作したいときにmapを用いて高速化を図りたい。
操作する関数が複数引数受け取るときに困ったのとリスト内法表記とどちらが早いのかを試したのでそのメモがてらまとめました。
リスト内法表記について初耳な方はこちらを参考にして頂ければと思います。

引数が1つの場合

たとえばリスト内の数字を全て2乗にしたいときは次のexample_map.pyのように書けます。

example_map.py
def square(x):
    return x**2

test = [1,2,3,4,5]
test_result = map(square, test)
print(list(test_result)) # [1,4,9,16,25]

これは以下のコードと等価です。

example_normal.py
def double(x):
    return x**2

test = [1,2,3,4,5]
test_result = []
for i in test:
    test_result.append(square(x))
print(test_result) # [1,4,9,16,25]

リスト内法表記については簡単なので省略。

引数が複数の場合

複数引数をとる関数にmapを適用させたいときとかあると思います。その時は以下のように書けます。

example_map_double.py
def matmul(x, y):
    return x*y

test1 = [1,2,3,4,5]
test2 = [3,4,5,6,7]
test_result = map(matmul, test1, test2)
print(list(test_result)) # [3,8,15,24,35]

これは以下のコードと等価です。enumerateについてはこことかを参考にして頂ければと思います。

example_normal_double.py
def matmul(x, y):
    return x*y

test1 = [1,2,3,4,5]
test2 = [3,4,5,6,7]
test_result = []
for i,k in enumerate(test1):
    test_result.append(matmul(k, test2[i]))
print(test_result) # [3,8,15,24,35]

また、同じpythonのリスト操作の高速化の手段としてリスト内法表記がありますが、複数引数の場合だと一旦zipに渡して、[(リスト1の要素1,リスト2の要素1),(リスト1の要素2,リスト2の要素2),(...,...),...]という形にしないといけません。下記のようになります。

example_normal_double.py
def matmul(x, y):
    return x*y

test1 = [1,2,3,4,5]
test2 = [3,4,5,6,7]
test_result = [matmul(k, d) for k,d in zip(test1, test2)]
print(test_result) # [3,8,15,24,35]

mapとどちらが便利かと言われたら微妙なところですね。

mapとリスト内法表記のスピード比較

スピードを比べてみました。異なる配列を用意してもやることは変わらないので同じ配列を各引数に入れる形にしました。また10回実行した平均を出しました。

example_speed_vs.py
def mutmal(x,y):

    return x*y

a = range(100000)

sum_time = 0
for k in range(10):
    start = time.time()
    b = map(mutmal, a,a)
    c = list(b)
    sum_time += time.time() - start
print(c[:5]) # [1,4,9,16,25]

print("finish in {}s".format(sum_time/10)) # 0.014s
sum_time = 0
for k in range(10):
    start = time.time()
    b = [mutmal(d,k) for d,k in zip(a,a)]
    sum_time += time.time() - start
print(b[:5]) # [1,4,9,16,25]

print("finish in {}s".format(sum_time/10)) # 0.017s

結果としてはあまり変わらないですね。関数の引数が3以上の場合でも使えます。

まとめ

mapとリスト内法表記、性能はどちらも変わらないので関数をリストの各要素に作用させたい場合はどっちを使ってもよさそうです。コードが短くなるmapの方が良いんですかね...

追記

@shiracamus さんからいただいたコメントを元に、%timeit を使ってjupyter notebookで実行しました。

def mutmal(x,y=3):    
    return x*y

a = range(100000)

%timeit b = list(map(mutmal, a))
# 14.6 ms ± 961 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit b = [mutmal(d) for d in a]
# 17 ms ± 972 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

標準偏差も出力されるのでこれは便利ですね、mapの方が少しだけ早い、という結論になります。