こんにちは。ナミレリです。みなさん、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つのバージョンで比較してみます。
環境は以下の通りです。
- 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 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)
- 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)

目次
はじめに
下記にもあるとおり、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の高速化や新機能については下記サイトを参考にさせていただきました。
また、この記事で比較する処理については下記のサイトを参考にさせていただきました。
計測環境について
以下の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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 0.945 | 0.978 | 0.994 | 0.847 |
M2 Air | 0.897 | 0.944 | 0.950 | 0.805 |
Intel Core i7 | 1.956 | 1.788 | 1.751 | 1.686 |
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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 1.071 | 1.165 | 1.241 | 1.074 |
M2 Air | 1.023 | 1.114 | 1.183 | 1.033 |
Intel Core i7 | 2.386 | 2.157 | 2.108 | 2.145 |
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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 1.107 | 1.139 | 0.993 | 0.846 |
M2 Air | 1.056 | 1.078 | 0.953 | 0.802 |
Intel Core i7 | 2.321 | 1.965 | 1.678 | 1.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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 4.832 | 5.848 | 6.088 | 5.321 |
M2 Air | 4.506 | 5.559 | 5.742 | 5.037 |
Intel Core i7 | 9.718 | 11.467 | 11.897 | 10.536 |
5. if文
0から100,000,000までの数字をforによりループさせ、if文の条件で繰り返し変数iが0だったら、という処理をしています。全体を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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 1.345 | 1.743 | 1.741 | 1.615 |
M2 Air | 1.266 | 1.648 | 1.658 | 1.547 |
Intel Core i7 | 2.517 | 2.924 | 2.849 | 2.984 |
配列アクセス
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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 2.111 | 2.313 | 2.470 | 2.331 |
M2 Air | 2.102 | 2.380 | 2.416 | 2.350 |
Intel Core i7 | 5.784 | 5.629 | 5.249 | 5.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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 3.063 | 3.922 | 4.177 | 4.038 |
M2 Air | 2.914 | 3.675 | 3.903 | 3.772 |
Intel Core i7 | 6.844 | 7.457 | 7.738 | 7.896 |
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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 3.514 | 4.272 | 4.456 | 4.278 |
M2 Air | 3.076 | 3.862 | 4.233 | 3.993 |
Intel Core i7 | 8.478 | 8.156 | 8.228 | 7.867 |
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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 0.409 | 0.557 | 0.638 | 0.580 |
M2 Air | 0.362 | 0.524 | 0.568 | 0.554 |
Intel Core i7 | 0.751 | 1.165 | 1.091 | 0.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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 0.464 | 0.488 | 0.496 | 0.483 |
M2 Air | 0.437 | 0.466 | 0.471 | 0.454 |
Intel Core i7 | 1.095 | 1.152 | 1.081 | 1.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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 0.835 | 1.010 | 1.139 | 1.086 |
M2 Air | 0.647 | 0.939 | 1.078 | 0.993 |
Intel Core i7 | 1.485 | 1.959 | 2.269 | 1.955 |
モジュール
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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 0.405 | 0.597 | 0.634 | 0.616 |
M2 Air | 0.357 | 0.567 | 0.586 | 0.564 |
Intel Core i7 | 0.873 | 1.181 | 1.156 | 1.113 |
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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 3.326 | 3.981 | 3.815 | 3.900 |
M2 Air | 3.133 | 3.636 | 3.439 | 3.621 |
Intel Core i7 | 5.213 | 5.966 | 6.120 | 6.098 |
14. 組み込み関数呼び出し
0から10,000,000までの数字をforによりループさせ、max関数により繰り返し変数iと0の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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 0.773 | 0.867 | 0.848 | 0.774 |
M2 Air | 0.733 | 0.797 | 0.896 | 0.743 |
Intel Core i7 | 1.340 | 1.456 | 1.434 | 1.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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 12.174 | 22.441 | 22.521 | 21.773 |
M2 Air | 11.412 | 20.648 | 20.200 | 20.065 |
Intel Core i7 | 18.515 | 36.845 | 33.072 | 29.316 |
numpy
16. numpy(デフォルト)
使われるBLASによって計算速度が変わります。BLASについては下記サイトがわかりやすくまとめてくれています。
下記はnumpyをpip 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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | 2.22153 | 2.31408 | 2.25946 | 2.19323 |
M2 Air | 1.98534 | 2.05553 | 1.97072 | 1.97072 |
Intel Core i7 | 1.32497 | 1.26241 | 1.29486 | 1.37860 |
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(英語版) がある。https://ja.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms
LINPACK ベンチマークの性能は、BLAS のサブルーチンである DGEMM(倍精度汎用行列乗算)の性能に大きく影響される。
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.0 | Python 3.10.6 | Python 3.9.15 | miniforge3-4.10 (Python 3.9.13) | |
M1 Max | – | – | – | 1.07296 |
M2 Air | – | – | – | 0.81686 |
Intel Core i7 | – | – | – | – |
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の比較)
M1 | Python 3.11.0 | Python 3.10.6 | 3.11.0 / 3.10.6 |
---|---|---|---|
forループ処理 | 0.945 | 0.978 | 0.966 |
代入処理 | 1.071 | 1.165 | 0.919 |
定数を使った四則演算 | 1.107 | 1.139 | 0.972 |
変数を使った四則演算 | 4.832 | 5.848 | 0.826 |
if文 | 1.345 | 1.743 | 0.772 |
配列シーケンシャル | 2.111 | 2.313 | 0.913 |
配列ランダムRead | 3.063 | 3.922 | 0.781 |
配列ランダムWrite | 3.514 | 4.272 | 0.823 |
ソート | 0.409 | 0.557 | 0.734 |
+による結合 | 0.464 | 0.488 | 0.951 |
joinによる結合 | 0.835 | 1.01 | 0.827 |
deque | 0.405 | 0.597 | 0.678 |
優先度付きキュー | 3.326 | 3.981 | 0.835 |
組み込み関数呼び出し | 0.773 | 0.867 | 0.892 |
再帰関数 | 12.174 | 22.441 | 0.542 |
まとめ(M2におけるPython 3.11.0とPython 3.10.6の比較)
M2 | Python 3.11.0 | Python 3.10.6 | 3.11.0 / 3.10.6 |
---|---|---|---|
forループ処理 | 0.897 | 0.944 | 0.950 |
代入処理 | 1.023 | 1.114 | 0.918 |
定数を使った四則演算 | 1.056 | 1.078 | 0.980 |
変数を使った四則演算 | 4.506 | 5.559 | 0.811 |
if文 | 1.266 | 1.648 | 0.768 |
配列シーケンシャル | 2.102 | 2.38 | 0.883 |
配列ランダムRead | 2.914 | 3.675 | 0.793 |
配列ランダムWrite | 3.076 | 3.862 | 0.796 |
ソート | 0.362 | 0.524 | 0.691 |
+による結合 | 0.437 | 0.466 | 0.938 |
joinによる結合 | 0.647 | 0.939 | 0.689 |
deque | 0.357 | 0.567 | 0.630 |
優先度付きキュー | 3.133 | 3.636 | 0.862 |
組み込み関数呼び出し | 0.733 | 0.797 | 0.920 |
再帰関数 | 11.412 | 20.648 | 0.553 |
まとめ(Intel Core i7におけるPython 3.11.0とPython 3.10.6の比較)
intel | Python 3.11.0 | Python 3.10.6 | 3.11.0 / 3.10.6 |
---|---|---|---|
forループ処理 | 1.956 | 1.788 | 1.094 |
代入処理 | 2.386 | 2.157 | 1.106 |
定数を使った四則演算 | 2.321 | 1.965 | 1.181 |
変数を使った四則演算 | 9.718 | 11.467 | 0.847 |
if文 | 2.517 | 2.924 | 0.861 |
配列シーケンシャル | 5.784 | 5.629 | 1.028 |
配列ランダムRead | 6.844 | 7.457 | 0.918 |
配列ランダムWrite | 8.478 | 8.156 | 1.039 |
ソート | 0.751 | 1.165 | 0.645 |
+による結合 | 1.095 | 1.152 | 0.951 |
joinによる結合 | 1.485 | 1.959 | 0.758 |
deque | 0.873 | 1.181 | 0.739 |
優先度付きキュー | 5.213 | 5.966 | 0.874 |
組み込み関数呼び出し | 1.34 | 1.456 | 0.920 |
再帰関数 | 18.515 | 36.845 | 0.503 |
まとめ(M1とM2の比較)
処理 | M1 Max (Python 3.11.0) | M2 Air (Python 3.11.0) | M2 / M1 |
---|---|---|---|
forループ処理 | 0.945 | 0.897 | 0.949 |
代入処理 | 1.071 | 1.023 | 0.955 |
定数を使った四則演算 | 1.107 | 1.056 | 0.954 |
変数を使った四則演算 | 4.832 | 4.506 | 0.933 |
if文 | 1.345 | 1.266 | 0.941 |
配列シーケンシャル | 2.111 | 2.102 | 0.996 |
配列ランダムRead | 3.063 | 2.914 | 0.951 |
配列ランダムWrite | 3.514 | 3.076 | 0.875 |
ソート | 0.409 | 0.362 | 0.885 |
+による結合 | 0.464 | 0.437 | 0.942 |
joinによる結合 | 0.835 | 0.647 | 0.775 |
deque | 0.405 | 0.357 | 0.881 |
優先度付きキュー | 3.326 | 3.133 | 0.942 |
組み込み関数呼び出し | 0.773 | 0.733 | 0.948 |
再帰関数 | 12.174 | 11.412 | 0.937 |
最後に
最後まで読んでいただきありがとうございます。今回のPython 3.11.0のベンチマークをいろいろとやってみるはいかがでしたでしょうか。Apple SiliconのM1やM2とPython3.11.0の組み合わせはIntelのそれと比べてもとても高速になっていることがわかります。M1とM2で比較してみてもM2のパフォーマンスが凄いです。
MacやLinux、Pythonなど技術系のkindle本も豊富にあります。詳しくはこちらから。
Amazonの電子書籍読み放題サービス「Kindle Unlimited」でプライム会員を対象に、最初の3か月間を無料体験できるキャンペーンを実施中。マンガ、小説、ビジネス書、雑誌など500万冊から、好きな本を何冊でも読めるキャンペーンです。
初めてkindle unlimited 読み放題をご利用の方は30日間の無料で体験できます。
期間終了後は月額980円で、いつでもキャンセルできます。
200万冊以上が読み放題。お好きな端末で利用可能です。
