Amazon オーディブル2ヶ月無料キャンペーン中 5/9まで

【Python】Python 3.11.0のベンチマークをいろいろとやってみる

14 min

こんにちは。ナミレリです。みなさん、MacでPythonは使っていますか?
最新のPython 3.11.0の高速化を試してみたく、M1 Max、M2 Air、Intel Core i7のMacbookでそれぞれベンチマークを試してみました。M1やM2、Corei7の比較はネット上であまり見かけないのでやってみました。また、Pythonのバージョンは3.11.0、3.10.6、3.9.15、miniforge3-4.10(Python 3.9.13)の4つのバージョンで比較してみます。

環境は以下の通りです。

この記事の計測環境
M1 Max
  • MacBook Pro 14インチ M1Max(メモリ32GB)
  • macOS Ventura 13.0.1
  • pyenv 2.3.6
  • Python 3.11.0、3.10.6、3.9.15、miniforge3-4.10(Python 3.9.13)
M2 Air
  • M2 MacBook Air 13.6 インチ(メモリ16GB)
  • macOS Ventura 13.0.1
  • pyenv 2.3.6
  • Python 3.11.0、3.10.6、3.9.15、miniforge3-4.10(Python 3.9.13)
Intel Core i7
  • MacBook Pro 13インチ2019(メモリ16GB)
  • macOS Big Sur 11.6.8
  • pyenv 2.3.6
  • Python 3.11.0、3.10.6、3.9.15、miniforge3-4.10(Python 3.9.13)
Parallels 19 for Macの無料トライアル もありますので、ぜひダウンロードして試してみてください。M1/M2/M3のMac上で快適にMacやUbuntu、Windowsが動作します。
NEW Parallels Desktop 19 for Mac

Parallels Desktop 19 for Macは、M1/M2/M3のMac上で快適にMacやUbuntu、Windowsが動作します。

14日間の無料トライアルもありますので、ぜひダウンロードして試してみてください。

はじめに

下記にもあるとおり、Python 3.11の最大のニュースはなんといっても高速化されていることです。いよいよ本気モードってことですね。公式サイトにはpyperformanceによるベンチマーク結果が掲載されています。

Python 3.11で最大のニュースは、なんと言っても Faster CPython: CPython 高速化計画 が開始されたことでしょう。CPython 高速化計画は、Mark Shannon氏が提案したプランに基づいてPythonの高速化を行うもので、Pythonを毎年50%高速化し、互換性を保ちつつ 4年間で5倍高速化する ことを目標としています。この計画はMicrosoft社の出資を獲得し、Pythonの父であるGuido van Rossum氏も加わって開発が進められています。

https://www.python.jp/news/wnpython311/index.html

Python 3.11.0の高速化や新機能については下記サイトを参考にさせていただきました。

また、この記事で比較する処理については下記のサイトを参考にさせていただきました。

Amazonの読み放題・聴き放題

kindle unlimited 読み放題
200万冊以上が読み放題

Audible
12万以上の対象作品が聴き放題

Amazon unlimited:最初の2ヶ月0円で読み放題(4/22まで)→ 詳しくはこちら

Amazon オーディブル:2ヶ月無料キャンペーン中(5/9まで)→ 詳しくはこちら

計測環境について

以下の3つのMacBookを使います。

M1 Max


sysctl -a | grep -e brand_string -e cpu.core_count
machdep.cpu.core_count: 10
machdep.cpu.brand_string: Apple M1 Max

M2 Air


sysctl -a | grep -e brand_string -e cpu.core_count
machdep.cpu.core_count: 8
machdep.cpu.brand_string: Apple M2

Intel Core i7


sysctl -a | grep -e brand_string -e cpu.core_count
machdep.cpu.brand_string: Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz
machdep.cpu.core_count: 4

1. forループ処理

単純なforによるループ処理を計測してみます。0から100,000,000までの数字をforによりループさせ、それを10回繰り返す処理です。処理が終わったら平均を秒で出力します。


import timeit

def test():
    for i in range(n):
        pass

n = 100000000  # 10^8
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max0.9450.9780.9940.847
M2 Air0.8970.9440.9500.805
Intel Core i71.9561.7881.7511.686
表:forループ処理

2. 代入処理

上の単純なforによるループ処理に加えて、ans = iの代入を追加したプログラムです。

0から100,000,000までの数字をforによりループさせ、都度その数値をansに代入しています。それを10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def test():
    ans = 0
    for i in range(n):
        ans = i

n = 100000000  # 10^8
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max1.0711.1651.2411.074
M2 Air1.0231.1141.1831.033
Intel Core i72.3862.1572.1082.145
表:代入処理
Amazonの読み放題・聴き放題

kindle unlimited 読み放題
200万冊以上が読み放題

Audible
12万以上の対象作品が聴き放題

Amazon unlimited:最初の2ヶ月0円で読み放題(4/22まで)→ 詳しくはこちら

Amazon オーディブル:2ヶ月無料キャンペーン中(5/9まで)→ 詳しくはこちら

3. 定数を使った四則演算

0から100,000,000までの数字をforによりループさせ、都度、決まった四則演算を行っています。10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def test():
    for i in range(n):
        1 + 1
        1 - 1
        1 * 1
        1 // 1
        
n = 100000000  # 10^8
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max1.1071.1390.9930.846
M2 Air1.0561.0780.9530.802
Intel Core i72.3211.9651.6781.647
表:定数を使った四則演算

4. 変数を使った四則演算

2から100,000,000までの数字をforによりループさせ、繰り返し変数iを使って四則演算を行っています。10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def test():
    for i in range(2, n):
        i * (i % (i - 1)) + i
        
n = 100000000  # 10^8
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max4.8325.8486.0885.321
M2 Air4.5065.5595.7425.037
Intel Core i79.71811.46711.89710.536
表:変数を使った四則演算

5. if文

0から100,000,000までの数字をforによりループさせ、if文の条件で繰り返し変数i0だったら、という処理をしています。全体を10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def test():
    for i in range(n):
        if i == 0:
            pass
    
n = 100000000  # 10^8
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max1.3451.7431.7411.615
M2 Air1.2661.6481.6581.547
Intel Core i72.5172.9242.8492.984
表:if文

配列アクセス

6. シーケンシャル

0から100,000,000までのリストAを用意して、順番にリストの要素にアクセスします。全体を10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def test():
    A = [i for i in range(n)]
    for a in A:
        pass

n = 100000000  # 10^8
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max2.1112.3132.4702.331
M2 Air2.1022.3802.4162.350
Intel Core i75.7845.6295.2495.523
表:配列アクセス

7. ランダムアクセス(Read)

0から100,000,000までのリストAを用意して、indexを指定してリストの要素にアクセスします。全体を10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def test():
    A = [i for i in range(n)]
    for i in range(n):
        A[i]

n = 100000000  # 10^8
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max3.0633.9224.1774.038
M2 Air2.9143.6753.9033.772
Intel Core i76.8447.4577.7387.896
表:ランダムアクセス(Read)

8. ランダムアクセス(Write)

0から100,000,000までのリストAを用意して、indexを指定してリストの要素にアクセスします。その要素に0を代入します。全体を10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def test():
    A = [i for i in range(n)]
    for i in range(n):
        A[i] = 0

n = 100000000  # 10^8
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max3.5144.2724.4564.278
M2 Air3.0763.8624.2333.993
Intel Core i78.4788.1568.2287.867
表: ランダムアクセス(Write)

9. ソート

0から1,000,000までランダムな順番のリストAを用意し、その後リストAをソートします。全体を10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit
from random import randint, seed
seed(0)

def test():
    A = [randint(1, n) for _ in range(n)]
    A.sort()

n = 1000000  # 10^6
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max0.4090.5570.6380.580
M2 Air0.3620.5240.5680.554
Intel Core i70.7511.1651.0910.937
表:ソート

文字列結合

10. +による結合

0から10,000,000までの数字をforによりループさせ、都度、文字列aを結合します。10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def test():
    ans = ''
    for i in range(n):
        ans += 'a'

n = 10000000  # 10^7
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max0.4640.4880.4960.483
M2 Air0.4370.4660.4710.454
Intel Core i71.0951.1521.0811.025
表: +による結合

11. joinによる結合

0から10,000,000までのリストA(文字列)を用意して、リストの各要素を区切り文字なしで結合します。10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def test():
    A = [str(i) for i in range(n)]
    ans = ''.join(A)

n = 10000000  # 10^7
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max0.8351.0101.1391.086
M2 Air0.6470.9391.0780.993
Intel Core i71.4851.9592.2691.955
表: joinによる結合

モジュール

12. deque

0から10,000,000までのdequeオブジェクトを用意し、0から10,000,000までの数字をforによりループさせ、dequeオブジェクトの先頭の要素を取り出し(que.popleft())、その要素を末尾に追加します(que.append())。10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit
from collections import deque

def test():
    que = deque(range(n))
    for i in range(n):
        que.append(que.popleft())

n = 10000000  # 10^7
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max0.4050.5970.6340.616
M2 Air0.3570.5670.5860.564
Intel Core i70.8731.1811.1561.113
表:deque

13. 優先度付きキュー

Python標準ライブラリのheapqを使い、リストqueに0から10,000,000までを優先度付きキューの要素に挿入します。その後、優先度付きキューから最小値を取り出します。この処理を10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit
import heapq

def test():
    que = [0]
    for i in range(n):
        heapq.heappush(que, i)

    for i in range(n):
        heapq.heappop(que)

n = 10000000  # 10^7
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max3.3263.9813.8153.900
M2 Air3.1333.6363.4393.621
Intel Core i75.2135.9666.1206.098
表:優先度付きキュー

14. 組み込み関数呼び出し

0から10,000,000までの数字をforによりループさせ、max関数により繰り返し変数i0の2つの要素の内、最大値を持つ要素の値を取得します。この処理を10回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def test():
    for i in range(n):
        max(0, i)

n = 10000000  # 10^7
loop = 10

result = timeit.timeit('test()', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max0.7730.8670.8480.774
M2 Air0.7330.7970.8960.743
Intel Core i71.3401.4561.4341.092
表:組み込み関数呼び出し

15. 再帰関数

40番目のフィボナッチ数列を再帰処理で求めます。3回繰り返し、処理が終わったら平均を秒で出力します。


import timeit

def fibonacci(n):
    if n == 0 or n == 1:
        return n
    else:
        return fibonacci(n - 2) + fibonacci(n - 1)

n = 40
loop = 3

result = timeit.timeit('fibonacci(n)', globals=globals(), number=loop)
print(format(result / loop, '.3f'))
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max12.17422.44122.52121.773
M2 Air11.41220.64820.20020.065
Intel Core i718.51536.84533.07229.316
表:再帰関数

numpy

16. numpy(デフォルト)

使われるBLASによって計算速度が変わります。BLASについては下記サイトがわかりやすくまとめてくれています。

下記はnumpypip installしたデフォルトの状態で計測です。


import time
import numpy as np
np.random.seed(42)
a = np.random.uniform(size=(300, 300))
runtimes = 10

timecosts = []
for _ in range(runtimes):
    s_time = time.time()
    for i in range(100):
        a += 1
        np.linalg.svd(a)
    timecosts.append(time.time() - s_time)

print(f'mean of {runtimes} runs: {np.mean(timecosts):.5f}s')
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max2.221532.314082.259462.19323
M2 Air1.985342.055531.970721.97072
Intel Core i71.324971.262411.294861.37860
表:numpy

Numpyの定番オススメ書籍:Pythonによるデータ分析入門 第2版

17. numpy("libblas=*=*accelerate"

次にlibblasを指定したnumpyでの計測です。condaでnumpyをインストールします。2倍程度に高速化しました。

Basic Linear Algebra Subprograms(BLAS)は数値線形代数の基礎的演算に必要な関数を定義するAPIである[1]。ベクトル・行列演算を含む38の関数からなるLevel 1 BLASが1979年に発表されたのち[2]、Level 2 および Level 3 まで拡張された。多数の実装が作成・整備され続けており、この分野におけるデファクトスタンダードとなっている。BLASの基礎演算を利用してLAPACKなどの上位パッケージが構築されており、科学技術計算・高性能計算で多用される。


BLASの関数を多用するソフトウェアにおいてBLAS実装(ライブラリ)の質は速度に直結する。高度な最適化は実装が動くハードウェアにも依存するため、特定CPUに特化したライブラリが提供される場合もある(インテルCPU向け: Intel Math Kernel Library)。オープンソースの最適化 BLAS 実装として OpenBLAS や ATLAS(英語版) がある。


LINPACK ベンチマークの性能は、BLAS のサブルーチンである DGEMM(倍精度汎用行列乗算)の性能に大きく影響される。

https://ja.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms

conda install numpy "libblas=*=*accelerate"


import time
import numpy as np
np.random.seed(42)
a = np.random.uniform(size=(300, 300))
runtimes = 10

timecosts = []
for _ in range(runtimes):
    s_time = time.time()
    for i in range(100):
        a += 1
        np.linalg.svd(a)
    timecosts.append(time.time() - s_time)

print(f'mean of {runtimes} runs: {np.mean(timecosts):.5f}s')
Python 3.11.0Python 3.10.6Python 3.9.15miniforge3-4.10
(Python 3.9.13)
M1 Max1.07296
M2 Air0.81686
Intel Core i7
表: numpy("libblas=*=*accelerate"

Numpyの定番オススメ書籍:Pythonによるデータ分析入門 第2版

numpyの設定状態は下記です。


>>> import numpy as np
>>> np.__config__.show()
blas_info:
    libraries = ['cblas', 'blas', 'cblas', 'blas']
    library_dirs = ['/Users/user/.pyenv/versions/miniforge3-4.10.3-10/lib']
    include_dirs = ['/Users/user/.pyenv/versions/miniforge3-4.10.3-10/include']
    language = c
    define_macros = [('HAVE_CBLAS', None)]
blas_opt_info:
    define_macros = [('NO_ATLAS_INFO', 1), ('HAVE_CBLAS', None)]
    libraries = ['cblas', 'blas', 'cblas', 'blas']
    library_dirs = ['/Users/user/.pyenv/versions/miniforge3-4.10.3-10/lib']
    include_dirs = ['/Users/user/.pyenv/versions/miniforge3-4.10.3-10/include']
    language = c
lapack_info:
    libraries = ['lapack', 'blas', 'lapack', 'blas']
    library_dirs = ['/Users/user/.pyenv/versions/miniforge3-4.10.3-10/lib']
    language = f77
lapack_opt_info:
    libraries = ['lapack', 'blas', 'lapack', 'blas', 'cblas', 'blas', 'cblas', 'blas']
    library_dirs = ['/Users/user/.pyenv/versions/miniforge3-4.10.3-10/lib']
    language = c
    define_macros = [('NO_ATLAS_INFO', 1), ('HAVE_CBLAS', None)]
    include_dirs = ['/Users/user/.pyenv/versions/miniforge3-4.10.3-10/include']
Supported SIMD extensions in this NumPy install:
    baseline = NEON,NEON_FP16,NEON_VFPV4,ASIMD
    found = ASIMDHP,ASIMDDP
    not found = 

まとめ(M1におけるPython 3.11.0とPython 3.10.6の比較)

M1Python 3.11.0Python 3.10.63.11.0 / 3.10.6
forループ処理0.9450.9780.966
代入処理1.0711.1650.919
定数を使った四則演算1.1071.1390.972
変数を使った四則演算4.8325.8480.826
if文1.3451.7430.772
配列シーケンシャル2.1112.3130.913
配列ランダムRead3.0633.9220.781
配列ランダムWrite3.5144.2720.823
ソート0.4090.5570.734
+による結合0.4640.4880.951
joinによる結合0.8351.010.827
deque0.4050.5970.678
優先度付きキュー3.3263.9810.835
組み込み関数呼び出し0.7730.8670.892
再帰関数12.17422.4410.542

まとめ(M2におけるPython 3.11.0とPython 3.10.6の比較)

M2Python 3.11.0Python 3.10.63.11.0 / 3.10.6
forループ処理0.8970.9440.950
代入処理1.0231.1140.918
定数を使った四則演算1.0561.0780.980
変数を使った四則演算4.5065.5590.811
if文1.2661.6480.768
配列シーケンシャル2.1022.380.883
配列ランダムRead2.9143.6750.793
配列ランダムWrite3.0763.8620.796
ソート0.3620.5240.691
+による結合0.4370.4660.938
joinによる結合0.6470.9390.689
deque0.3570.5670.630
優先度付きキュー3.1333.6360.862
組み込み関数呼び出し0.7330.7970.920
再帰関数11.41220.6480.553

まとめ(Intel Core i7におけるPython 3.11.0とPython 3.10.6の比較)

intelPython 3.11.0Python 3.10.63.11.0 / 3.10.6
forループ処理1.9561.7881.094
代入処理2.3862.1571.106
定数を使った四則演算2.3211.9651.181
変数を使った四則演算9.71811.4670.847
if文2.5172.9240.861
配列シーケンシャル5.7845.6291.028
配列ランダムRead6.8447.4570.918
配列ランダムWrite8.4788.1561.039
ソート0.7511.1650.645
+による結合1.0951.1520.951
joinによる結合1.4851.9590.758
deque0.8731.1810.739
優先度付きキュー5.2135.9660.874
組み込み関数呼び出し1.341.4560.920
再帰関数18.51536.8450.503

まとめ(M1とM2の比較)

処理M1 Max
(Python 3.11.0)
M2 Air
(Python 3.11.0)
M2 / M1
forループ処理0.9450.8970.949
代入処理1.0711.0230.955
定数を使った四則演算1.1071.0560.954
変数を使った四則演算4.8324.5060.933
if文1.3451.2660.941
配列シーケンシャル2.1112.1020.996
配列ランダムRead3.0632.9140.951
配列ランダムWrite3.5143.0760.875
ソート0.4090.3620.885
+による結合0.4640.4370.942
joinによる結合0.8350.6470.775
deque0.4050.3570.881
優先度付きキュー3.3263.1330.942
組み込み関数呼び出し0.7730.7330.948
再帰関数12.17411.4120.937

最後に

最後まで読んでいただきありがとうございます。今回のPython 3.11.0のベンチマークをいろいろとやってみるはいかがでしたでしょうか。Apple SiliconのM1やM2とPython3.11.0の組み合わせはIntelのそれと比べてもとても高速になっていることがわかります。M1とM2で比較してみてもM2のパフォーマンスが凄いです。

MacやLinux、Pythonなど技術系のkindle本も豊富にあります。詳しくはこちらから。

初めてkindle unlimited 読み放題をご利用の方は30日間の無料で体験できます。
期間終了後は月額980円で、いつでもキャンセルできます。
200万冊以上が読み放題。お好きな端末で利用可能です。

定番おすすめ記事

関連記事