やりたいこと
numpy.ndarrayでdequeみたいなことがしたい場合、単純にnp.append(x, y)とnp.delete(x, 0)とするのが速いのか、それともdequeを使ってからndarrayにしたほうがいいのかというのを検証します。
x = np.append(x, 1)
x = np.delete(x, 0)
最後に1という値を追加して先頭を削除しています。
numpyはarray作成時に固定長の領域を確保するみたいなので長さを変えるとなんだか遅そうです。今回の検証はFIFO【先入先出法 / First-In First-Out / 先入れ先出し】の話しみたいですが、先頭を読み出さず全体を読む場合を想定してます。つまりバッファーのような使い方の場合の検証となっています。
追加するごとにnumpy.mean()やnumpy.std()を使いたいので、最後はnumpy.ndarrayになるように条件を限定しました。単純に配列を作るだけならdequeをそのまま使ったほうが速いですが、それだとnumpyのmean()やstd()をそのままつかえないのです。
今回はループごとにnumpyの関数を使うことを仮定して、変換コストも込みで比べてみたいと思います。
代替候補
numpy.roll()を使う
x = np.roll(x, -1)
x[-1] = 1
もっとも素朴にdequeのようにひとつだけずらして一番うしろに代入します。
appendしてdeleteする
x = np.append(x, 1)
x = np.delete(x, 0)
やりたいことそのままです。これが遅そうなので今回検証しています。
一つ前にコピーして最後に代入する
x[0:-1] = x[1:]
x[-1] = 1
スライスは速そうなので検証してみます。rollと同じようなことやってるように見えますが、微妙に違います。
dequeを使って操作してからnumpy.ndarrayに変換する
d.append(1)
x = np.array(d)
ここでdはd=deque(maxlen)で生成したdequeです。maxlenを指定すると勝手に先頭が消えます。単純にappendするだけだとdequeが圧倒的に速いんですが今回は一回ごとにnumpy.arrayに変換する必要があります。
検証コード
それではどれが一番早いのか検証です。検証に使ったコードはこちら。
import time
import numpy as np
from collections import deque
# バッファーの長さ
xlen = 1000
# シフトする回数
n = 100000
x = np.zeros(xlen)
s = time.time()
for i in range(n):
x = np.roll(x, -1)
x[-1] = 1
print(time.time() - s)
s = time.time()
for i in range(n):
x = np.append(x, 1)
x = np.delete(x, 0)
print(time.time() - s)
s = time.time()
for i in range(n):
x[0:-1] = x[1:]
x[-1] = 1
print(time.time() - s)
# dequeの宣言と初期化
d = deque(maxlen=xlen)
for i in range(xlen):
d.append(1)
s = time.time()
for i in range(n):
d.append(1)
x = np.array(d)
print(time.time() - s)
検証結果
python3.8で実行しました。シフト操作回数は10000回で測定しました。長さはアレイの長さxlenのことです。
方法 | 長さ100の場合 | 長さ10000の場合 |
---|---|---|
numpy.roll()を使う | 2.58s | 3.28s |
appendしてdeleteする | 1.79s | 2.78s |
一つ前にコピーして最後に代入する | 0.100s | 0.366s |
dequeを使って操作してからnumpy.ndarrayに変換する | 1.52s | 88.0s |
最も速いのはスライスでコピーして最後に代入するという結果になりました。
疑惑のコードも間違った選択ではなさそうです。
dequeも配列が短いときにはnp.array()がそれほど遅くないのでいいのかもしれないです。
長くなるとnp.array()の時間がとてつもなくかかるのでやばいです。
何かの参考になれば幸いです。