LoginSignup
2
2

More than 3 years have passed since last update.

pythonにおけるlist、numpy.arrayの複製速度(&代入速度)

Last updated at Posted at 2019-07-11

先日投稿した「pythonにおけるlist、numpy.arrayの複製、初期化(0埋め)」という記事でlist、ndarrayの幾つかの複製方法について触れましたが、今回の記事では、各複製方法の速度を測定しました。

結果

1次元list・ndarrayのそれぞれの場合で、同じ長さの配列を用意し、それの複製速度を測定しました。前回の記事ではcopyモジュールのdeepcopyは使いませんでしたが、今回は方法(6)として、deepcopyによる複製速度も測定しました。

1次元listの複製

長さ1000の1次元list aを用意して、方法(2)〜(6)のそれぞれの方法でaを複製する関数を定義し、処理速度を測定した結果が次の表です。

番号 方法 処理速度の平均[秒] 標準偏差[秒]
(2) copy_a = a[:] 0.00352 0.000101
(3) copy_a = a.copy() 0.00353 0.000115
(4) copy_a = list(a) 0.00344 0.000156
(5) copy_a = [elem for elem in a] 0.017 0.000632
(6) copy_a = deepcopy(a) 0.405 0.0259

list aが高次元配列の場合、方法(6)のdeepcopyは複製に重宝しますが、実行速度は最も遅い結果となりました。

1次元ndarrayの複製

長さ1000の1次元ndarray aを用意して、方法(3)〜(6)のそれぞれの方法でaを複製する関数を定義し、処理速度を測定した結果が次の表です。

番号 方法 処理速度の平均[秒] 標準偏差[秒]
(3) copy_a = a.copy() 0.00053 6.17e-05
(4) copy_a = np.array(a) 0.000568 8.08e-05
(5) copy_a = np.array([elem for elem in a]) 0.093 0.00314
(6) copy_a = deepcopy(a) 0.00172 0.000179

aがlistのときと比べると、ndarrayの方が全体的に複製速度が速いですね。方法(5)は一度listを作ってndarrayに変換しているため1次元listの複製のときよりも遅い結果となっています。

測定方法

次のスクリプトを実行して処理速度の測定を行いました。

from mytools import measure_time
import numpy as np
from copy import deepcopy


def do_nothing():
    pass

def copy_list2(_list):
    new_list = _list[:]
    del new_list

def copy_list3(_list):
    new_list = _list.copy()
    del new_list

def copy_list4(_list):
    new_list = list(_list)
    del new_list

def copy_list5(_list):
    new_list = [elem for elem in _list]
    del new_list

def copy_list6(_list):
    new_list = deepcopy(_list)
    del new_list

def copy_array3(_array):
    new_array = _array.copy()
    del new_array

def copy_array4(_array):
    new_array = np.array(_array)
    del new_array

def copy_array5(_array):
    new_array = np.array([elem for elem in _array])
    del new_array

def copy_array6(_array):
    new_array = deepcopy(_array)
    del new_array

if __name__ == '__main__':
    N = 1000
    _list = list([0 for _ in range(N)])
    _array = np.array([0 for _ in range(N)])

    measure_time(do_nothing)

    measure_time(copy_list2, _list)
    measure_time(copy_list3, _list)
    measure_time(copy_list4, _list)
    measure_time(copy_list5, _list)
    measure_time(copy_list6, _list)

    measure_time(copy_array3, _array)
    measure_time(copy_array4, _array)
    measure_time(copy_array5, _array)
    measure_time(copy_array6, _array)

次が、上記スクリプト中のmeasure_time関数の定義です。

import sys
import time
import numpy as np


def measure_time(func, *args, iteration=1000, samples=1000):
    _tuple = (func.__name__, iteration, samples)
    print('Function: {}\nIteration: {}\nSamples: {}'.format(*_tuple))
    series = []
    for i in range(samples):
        sys.stdout.write('\rProgress: [{}/{}]'.format(i, samples))
        start = time.time()
        for j in range(iteration):
            func(*args)
        series.append(time.time()-start)
    print('\rMean: {}, SD: {}\n'.format(np.average(series), np.std(series)))

measure_time関数は、引数として渡された関数がiteration回実行されるのに掛かる時間をsamples回記録して、処理時間の平均と標準偏差を標準出力します。以下がmeasure_timeの実行例です。

Function: do_nothing
Iteration: 1000
Samples: 1000
Mean: 9.026002883911132e-05, SD: 9.253773932953281e-06

Function: copy_list2
Iteration: 1000
Samples: 1000
Mean: 0.0034766535758972168, SD: 6.660195155484076e-05

備考

上記スクリプト中の関数copy_array4では、ndarrayを受け取ってnp.array()により新たにndarrayオブジェクトを作っていますが、copy_array4に上記スクリプト中の_listを渡した場合の処理速度を測定してみました。

Function: copy_array4
Iteration: 1000
Samples: 1000
Mean: 0.04266641592979431, SD: 0.00023159095884393903

ndarrayを渡されたときよりも100倍程遅いですね。加えて、関数copy_list4に同じ_listを渡したときよりも10倍程遅いです。

逆に、関数copy_list4にスクリプト中の_arrayを渡した場合の処理速度を測定してみましょう。

Function: copy_list4
Iteration: 1000
Samples: 1000
Mean: 0.029821698427200317, SD: 0.004067780773085223

copy_array4に_listを渡した場合の平均処理速度と同じオーダーになりました。

listオブジェクトを引数にnp.array()でndarrayオブジェクトを作ったり、ndarrayオブジェクトを引数にlist()でlistオブジェクトを作るのって時間掛かるんですね。

追記(代入速度の測定)

興味本位で、次のスクリプトにより1次元list・ndarrayの各要素を書き換える操作の処理速度を測定してみました。

from mytools import measure_time
import numpy as np


def for_only(N):
    for n in range(N):
        pass

def substitute_list(_list, N):
    for n in range(N):
        _list[n] = 0
    del _list

def substitute_array(_array, N):
    for n in range(N):
        _array[n] = 0
    del _array

if __name__ == '__main__':
    N = 1000
    _list = list([0 for _ in range(N)])
    _array = np.array([0 for _ in range(N)])

    measure_time(dummy, _list, N)
    measure_time(substitute_list, _list, N)
    measure_time(substitute_array, _array, N)

以下がこのスクリプトの実行結果です。

Function: for_only
Iteration: 1000
Samples: 1000
Mean: 0.01462605857849121, SD: 0.0008302230687152986

Function: substitute_list
Iteration: 1000
Samples: 1000
Mean: 0.02975408983230591, SD: 0.0016898342277376784

Function: substitute_array
Iteration: 1000
Samples: 1000
Mean: 0.0648690721988678, SD: 0.00520460908502261

関数for_onlyはfor文をただ回すだけの関数なので、for文の実行時間文を差し引くと、listオブジェクトの要素を書き換える方がndarrayオブジェクトの場合よりも3倍程速い結果となります。

リンク

2
2
0

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
2
2