はじめに
「NumPyは速い」と言われますが、処理内容とデータサイズで差は大きく変わります。
この記事では、Python標準のリスト実装とNumPy実装を同じ処理で比較し、97ケースを計測しました。
先に全体傾向です。
- NumPyが遅かったのは97件中2件(どちらも小規模データ)
- 速度倍率(Python/NumPy)の中央値は 22.9倍
- 大規模データでは100倍超が何度も出る
検証コードのレポジトリ (likeお願いします)
実際のコードと比較結果(平均倍率が高い順)
各項目は「同じ入力データ」を Python(list) と NumPy(ndarray) で比較しています。
1) 行列積(Matrix multiplication / GEMM)
Python(list)
py_list_result = [[sum(A_list[i][k] * B_list[k][j] for k in range(n)) for j in range(n)] for i in range(n)]
NumPy(ndarray)
np_arr_result = A_arr @ B_arr
速度:
-
N=100: 4430.5x
2) 線形代数(Linear algebra)
Python(list)
py_list_result = [sum(x * y for x, y in zip(row, vec_list)) for row in mat_list]
NumPy(ndarray)
np_arr_result = mat_arr @ vec_arr
速度:
-
N=100: 292.8x -
N=500: 477.3x -
N=1,000: 422.6x
3) インデックス参照・スライシング・結合(Indexing, slicing, concatenation)
Python(list)
for i in range(n): py_list_work[i] += 1.0
NumPy(ndarray)
np_arr_work += 1.0
速度:
-
N=100: 7.8x -
N=10,000: 327.6x -
N=1,000,000: 289.6x
4) 前処理・正規化(Preprocessing / Normalization)
Python(list)
py_list_result = [sum(data_list[i:i+5]) / 5 for i in range(len(data_list) - 4)]
NumPy(ndarray)
np_arr_result = np.convolve(data_arr, np.ones(5) / 5, mode="valid")
速度:
-
N=100: 17.4x -
N=10,000: 226.0x -
N=1,000,000: 277.2x
5) 集約・還元(Aggregation / Reduction)
Python(list)
py_list_result = [sum(row[c] for row in mat_list) for c in range(n)]
NumPy(ndarray)
np_arr_result = np.sum(mat_arr, axis=0)
速度:
-
N=100: 80.2x -
N=500: 173.7x -
N=1,000: 248.8x
6) 要素単位演算(Element-wise operations)
Python(list)
py_list_result = [x + 1.5 for x in data_list]
NumPy(ndarray)
np_arr_result = data_arr + 1.5
速度:
-
N=100: 5.8x -
N=10,000: 112.7x -
N=1,000,000: 181.1x
7) 条件選択・ブールマスキング(Conditional selection / Boolean masking)
Python(list)
py_list_result = [-1.0 if x < -0.5 else (1.0 if x > 0.5 else 0.0) for x in data_list]
NumPy(ndarray)
np_arr_result = np.where(data_arr < -0.5, -1.0, np.where(data_arr > 0.5, 1.0, 0.0))
速度:
-
N=100: 1.9x -
N=10,000: 22.9x -
N=1,000,000: 26.1x
8) 数学関数(Mathematical functions)
Python(list)
py_list_result = [math.log1p(abs(x)) for x in data_list]
NumPy(ndarray)
np_arr_result = np.log1p(np.abs(data_arr))
速度:
-
N=100: 6.5x -
N=10,000: 9.4x -
N=1,000,000: 8.9x
9) ソート・順位付け(Sorting / Ranking)
Python(list)
py_list_result = sorted(data_list, reverse=True)[:10]
NumPy(ndarray)
np_arr_result = np.sort(data_arr)[-10:][::-1]
速度:
-
N=100: 1.4x -
N=10,000: 3.8x -
N=200,000: 4.0x
全33種類の処理の実装式と平均倍率(高い順)
| カテゴリ(Category) | 処理名(Operation) | Python実装 | NumPy実装 | 平均倍率 |
|---|---|---|---|---|
| 行列積(Matrix multiplication / GEMM) | matmul |
[[sum(A[i][k]*B[k][j] for k in range(n)) for j in range(n)] for i in range(n)] |
A @ B |
4430.5x |
| インデックス参照・スライシング・結合(Indexing, slicing, concatenation) | slice_strided |
d[::2] |
d[::2] |
3204.5x |
| インデックス参照・スライシング・結合(Indexing, slicing, concatenation) | slice_contiguous |
d[start:end] |
d[start:end] |
1710.1x |
| 線形代数(Linear algebra) | matvec |
[sum(x * y for x, y in zip(row, v)) for row in m] |
m @ v |
397.5x |
| インデックス参照・スライシング・結合(Indexing, slicing, concatenation) | in_place_add |
for i in range(n): work[i] += 1.0 |
work[:] += 1.0 |
208.3x |
| 前処理・正規化(Preprocessing / Normalization) | rolling_mean_w5 |
[sum(d[i:i+5]) / 5 for i in range(len(d)-4)] |
np.convolve(d, np.ones(5)/5, mode="valid") |
173.5x |
| 集約・還元(Aggregation / Reduction) | sum_axis_0 |
[sum(row[c] for row in m) for c in range(n)] |
np.sum(m, axis=0) |
167.6x |
| 線形代数(Linear algebra) | broadcast_add |
[[x + y for x, y in zip(row, v)] for row in m] |
m + v |
101.8x |
| 要素単位演算(Element-wise operations) | scalar_add |
[x + 1.5 for x in d] |
d + 1.5 |
99.9x |
| 線形代数(Linear algebra) | dot_1d |
sum(x * y for x, y in zip(a, b)) |
np.dot(a, b) |
99.3x |
| インデックス参照・スライシング・結合(Indexing, slicing, concatenation) | copy_add |
new_d = [x + 1.0 for x in d] |
new_d = d + 1.0 |
94.7x |
| 要素単位演算(Element-wise operations) | scalar_mul |
[x * 1.5 for x in d] |
d * 1.5 |
90.2x |
| 集約・還元(Aggregation / Reduction) | max_axis_1 |
[max(row) for row in m] |
np.max(m, axis=1) |
64.4x |
| 要素単位演算(Element-wise operations) | affine_transform |
[(x * 1.1) + 0.3 for x in d] |
(d * 1.1) + 0.3 |
61.5x |
| 数学関数(Mathematical functions) | sqrt_abs |
[math.sqrt(abs(x) + 1e-12) for x in d] |
np.sqrt(np.abs(d) + 1e-12) |
59.3x |
| 前処理・正規化(Preprocessing / Normalization) | minmax_scale |
[(x - min_v) / (max_v - min_v) for x in d] |
(d - min_v) / (max_v - min_v) |
50.6x |
| 集約・還元(Aggregation / Reduction) | std_1d |
math.sqrt(sum((x-mean)**2 for x in d)/len(d)) |
np.std(d) |
48.2x |
| 前処理・正規化(Preprocessing / Normalization) | zscore |
[(x - mean) / std for x in d] |
(d - mean) / std |
43.4x |
| 前処理・正規化(Preprocessing / Normalization) | zscore_clip |
[max(-2.0, min(2.0, (x - mean) / std)) for x in d] |
np.clip((d - mean) / std, -2, 2) |
38.0x |
| 要素単位演算(Element-wise operations) | clip |
[1 if x > 1 else (-1 if x < -1 else x) for x in d] |
np.clip(d, -1, 1) |
35.6x |
| 条件選択・ブールマスキング(Conditional selection / Boolean masking) | binary_threshold |
[1.0 if x > 0 else 0.0 for x in d] |
np.where(d > 0, 1.0, 0.0) |
30.8x |
| 条件選択・ブールマスキング(Conditional selection / Boolean masking) | relu_fill |
[x if x > 0 else 0.0 for x in d] |
np.where(d > 0, d, 0.0) |
23.1x |
| 条件選択・ブールマスキング(Conditional selection / Boolean masking) | ternary_bucket |
[-1.0 if x < -0.5 else (1.0 if x > 0.5 else 0.0) for x in d] |
np.where(d < -0.5, -1.0, np.where(d > 0.5, 1.0, 0.0)) |
17.0x |
| 集約・還元(Aggregation / Reduction) | sum_axis_1 |
[sum(row) for row in m] |
np.sum(m, axis=1) |
12.9x |
| 集約・還元(Aggregation / Reduction) | mean_axis_1 |
[sum(row) / len(row) for row in m] |
np.mean(m, axis=1) |
12.1x |
| 数学関数(Mathematical functions) | sin |
[math.sin(x) for x in d] |
np.sin(d) |
9.8x |
| 集約・還元(Aggregation / Reduction) | quantile_90 |
sorted_d[round(0.9 * (len(d) - 1))] |
np.quantile(d, 0.9, method="nearest") |
9.0x |
| 数学関数(Mathematical functions) | log1p_abs |
[math.log1p(abs(x)) for x in d] |
np.log1p(np.abs(d)) |
8.3x |
| 数学関数(Mathematical functions) | exp |
[math.exp(x) for x in d] |
np.exp(d) |
8.1x |
| インデックス参照・スライシング・結合(Indexing, slicing, concatenation) | concat |
left + right |
np.concatenate((left, right)) |
5.7x |
| 条件選択・ブールマスキング(Conditional selection / Boolean masking) | filter_gt_0_5 |
[x for x in d if x > 0.5] |
d[d > 0.5] |
5.0x |
| ソート・順位付け(Sorting / Ranking) | top10_desc |
sorted(d, reverse=True)[:10] |
np.sort(d)[-10:][::-1] |
3.1x |
| ソート・順位付け(Sorting / Ranking) | sort_asc |
sorted(d) |
np.sort(d) |
3.1x |
結果(全体)
results_comprehensive.csv(97行)から集計しました。
- NumPyが遅いケース: 2/97
- 速度倍率の中央値: 22.9x
- 四分位範囲: 6.5x 〜 97.9x
- 最小: 0.2x(
quantile_90,N=100) - 最大: 9505.6x(
slice_strided,N=1,000,000)
カテゴリ平均(抜粋):
- ソート・順位付け(Sorting / Ranking): 3.1x
- 数学関数(Mathematical functions): 21.4x
- 前処理・正規化(Preprocessing / Normalization): 76.4x
- 線形代数(Linear algebra): 199.6x
- 行列積(Matrix multiplication / GEMM): 4430.5x
代表ケースの速度(高い順)
-
matmul(N=100): 4430.5x -
in_place_addvscopy_add(N=1,000,000): 289.6x vs 180.2x -
rolling_mean_w5(N=1,000,000): 277.2x -
sum_axis_0vssum_axis_1(N=1000): 248.8x vs 15.0x -
scalar_add(N=1,000,000): 181.1x -
top10_desc(N=200,000): 4.0x
NumPyが遅かったケース
-
quantile_90,N=100: 0.2x -
concat,N=100: 0.4x
いずれも小規模データで、NumPy呼び出しの固定コストが効いたケースです。
再現手順
./.venv/bin/python benchmark_business_comprehensive.py
生成物:
results_comprehensive.csvsummary_comprehensive.md
まとめ
今回の97ケース比較では、NumPyは多くの場面で高速でした。
ただし小規模データでは逆転も起きるため、実装の選択は処理内容とデータサイズで決めるのが確実です。
検証コードのレポジトリ (likeお願いします)