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

【Python】M1、M2、M3のMacbookでPython 3.12.2をベンチマークする

9 min

こんにちは。ナミレリです。みなさん、MacでPythonは使っていますか?

2024年3月にM3のMacbook Airが発売され、15インチのシルバーを購入しました。M1、M2、M3でPython 3.12.2のベンチマークテストを行いどの程度シングルコア性能が上がっているのかを確認していきます。

環境は以下の通りです。macOSはSonoma 14.4でPythonのバージョンは3.12.2に統一しています。

この記事の計測環境
M3 MacBook Air
  • M3 MacBook Air 15インチ(メモリ24GB)
  • macOS Sonoma 14.4
  • Python 3.12.2
M2 MacBook Air
  • M2 MacBook Air 13.6 インチ(メモリ16GB)
  • macOS Sonoma 14.4
  • Python 3.12.2
M1 Max MacBook Pro
  • MacBook Pro 14インチ M1Max(メモリ32GB)
  • macOS Sonoma 14.4
  • Python 3.12.2
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日間の無料トライアルもありますので、ぜひダウンロードして試してみてください。

計測環境について

以下の3つのMacBookを使います。sysctl -aの詳細です。

M3 MacBook Air


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

M2 MacBook Air


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

M1 Max MacBook Pro


sysctl -a | grep -e brand_string -e cpu.core_count
machdep.cpu.core_count: 10
machdep.cpu.brand_string: Apple M1 Max
Amazonの読み放題・聴き放題

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

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

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

ベンチマークスクリプトについて

下の記事を参考にさせていただきました。

また、Pythonの3.9、3.10、3.11などのベンチマークについて以前の記事で紹介しています。ぜひご覧ください。

では、早速はじめてみます。

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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
forループ処理1.1571.0920.769
表: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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
代入処理1.3111.0990.864
表:代入処理
Amazonの読み放題・聴き放題

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

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

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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
定数を使った四則演算1.2921.1000.901
表:定数を使った四則演算

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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
変数を使った四則演算5.6754.7973.926
表:変数を使った四則演算

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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
if文1.6151.3971.087
表: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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
配列アクセス2.3022.0591.630
表:配列アクセス

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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
ランダムアクセス(Read)3.5953.1802.435
表:ランダムアクセス(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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
ランダムアクセス(Write)3.7403.1822.492
表:ランダムアクセス(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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
ソート0.4550.4250.367
表:ソート

文字列結合

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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
+による結合0.4920.4580.385
表: +による結合

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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
joinによる結合0.6890.6450.529
表: 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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
deque0.5450.4260.340
表: 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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
優先度付きキュー2.8982.7562.325
表:優先度付きキュー

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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
組み込み関数呼び出し0.9180.9050.648
表:組み込み関数呼び出し

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'))
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
再帰関数11.37410.6078.754
表:再帰関数

numpy

16. numpy

libblasを指定したnumpyでの計測です。


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')
M1 Max MacBook ProM2 MacBook AirM3 MacBook Air
numpy1.0070.8060.706
表:numpy

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

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

最後に

最後まで読んでいただきありがとうございます。今回の【Python】M1、M2、M3のMacbookでPython 3.12.2をベンチマークするはいかがでしたでしょうか。M1の衝撃は今でも忘れることはないですが、M2、M3と性能がしっかり向上しているのがわかります。MacでPythonは楽しいですね。

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

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

定番おすすめ記事

関連記事