NumPyのndarray.all()やany()は短絡評価などをしてなさそう。

NumPyのall()やany()には短絡評価が実装されているのだろうと勝手に思っていた。
しかしそれはなさそうだと、実験によって確かめた記事です。

NumpyExercises 100
numpy-100/100_Numpy_exercises.md at master · rougier/numpy-100 · GitHub
の60において、0が並んでいるか?を確かめるために、以下のようなコードを書いた。

import numpy as np
import time as time
N = 100000000
a = np.random.rand(N, 1)
start_time = time.time()
print(0 in np.count_nonzero(a, axis=1))
end_time = time.time()
print(end_time-start_time)

start_time = time.time()
print(~a.all(axis=1).any())
end_time = time.time()
print(end_time-start_time)

乱数関数が1億もの0を出す確率はほぼ0であるため、高確率でどちらもFalseを出力する。
そして、どの行も高確率で最初の要素が0ではなく、その時点でFalseであることが確定する。
この性質を用いた評価は短絡評価と呼ばれている。

N = 10においてそれぞれの時間は
0.1001 ms
0.0231 ms

N = 1000において
0.1121 ms
0.1159 ms

N = 100万において
5.095 ms
0.9420 ms

N = 1億において
617.5 ms
92.59 ms
であり、傾向としてはallやany関数を用いた方が高速ではあった。
しかし要素数が100万から1億へと変化しても、時間は100倍となり、おそらく短絡評価は行われていない。

本題ではないが、「count_nonzeroと~aで反転する操作のどちらが高速か?」は自明ではないが、countが遅いようだ。

ちなみに
np.random.rand(100,1000000)では
244.2 ms
80.15 ms
np.random.rand(1000000, 100)では
216.1 ms
84.20 ms
となり余りメモリのアクセスに影響されていなかった。

更に
np.random.rand(1000000000, 1)において

z = (a==0)
start_time = time.time()
print(z.all(axis=0).any())
end_time = time.time()
print(end_time-start_time)

としてみた。
z = (a==0)には961.1 msかかっているが、
先ほどの2種類が
1894 ms(count_nonzero)
747.1 ms(~a.all(axis=1).any())
であるのに対し
25.58 ms
で実行した。
しかし、
なぜなら
N=10000で
0.0001693 ms
N=1000000で
0.5000 ms
となったため、単にboolの演算が高速なだけや、notを使っていないからだと思われる。
つまり、おそらく短絡評価は行われていない。