ハクソク

世界を動かす技術を、日本語で。

プログラマーが知っておくべきPythonの数値

概要

  • Pythonで知っておくべき基本的な性能指標のまとめ
  • 各種データ構造や操作の速度・メモリコストを比較
  • Webフレームワークやシリアライズのパフォーマンスも網羅
  • ベンチマーク環境の詳細も記載
  • 実践的な選択基準や最適化のヒントを提示

Pythonプログラマが知っておくべき数値一覧

  • Pythonの各操作やデータ構造のパフォーマンスを一覧化
  • 速度・メモリ消費量をカテゴリごとに整理
  • ベンチマーク環境:CPython 3.14.2、Mac Mini M4 Pro(ARM、14コア、24GB RAM、macOS Tahoe)
  • 相対比較が重要:自分の環境との差よりも、どの操作が速い・遅いかを重視
  • ベンチマークコードはGitHubで公開(python-numbers-everyone-should-know

メモリ消費量

  • 空のPythonプロセス:15.73MB
  • 文字列
    • 空文字列:41バイト
    • 100文字:141バイト
  • 数値
    • 小さなint(0〜256):28バイト
    • float:24バイト
  • リスト・辞書・セット
    • 空リスト:56バイト
    • 1,000個のintリスト:35.2KB
    • 空辞書:64バイト
    • 1,000要素辞書:63.4KB
    • 空セット:216バイト
    • 1,000要素セット:59.6KB
  • クラスインスタンス
    • 通常クラス(5属性):694バイト
    • __slots__クラス(5属性):212バイト
    • namedtuple:228バイト
    • dataclass:694バイト

基本操作の速度

  • 整数の加算:19.0ns(1秒間に5,270万回)
  • 浮動小数点加算:18.4ns
  • 文字列連結(+):39.1ns
  • f-string:64.9ns
  • .format():103ns
  • リストappend:28.7ns
  • リスト内包表記(1,000件):9.45μs
  • forループ(1,000件):11.9μs

コレクション操作

  • 辞書キー参照:21.9ns
  • セット会員判定:19.0ns
  • リストインデックスアクセス:17.6ns
  • リストin判定(1,000件):3.85μs
  • len():18.8ns(リスト1,000件)
  • リスト走査(1,000件):7.87μs
  • 辞書走査(1,000件):8.74μs

属性アクセス

  • 通常クラス属性読み:14.1ns
  • __slots__クラス属性読み:14.1ns
  • @property読み:19.0ns
  • getattr():13.8ns
  • hasattr():23.8ns

JSON・シリアライズ

  • json.dumps()(単純):708ns
  • json.loads()(単純):714ns
  • orjson.dumps()(複雑):310ns
  • ujson.dumps()(複雑):1.64μs
  • msgspec encode(複雑):445ns
  • Pydantic model_dump_json():1.54μs

Webフレームワーク

  • Flask(JSON返却):16.5μs
  • Django(JSON返却):18.1μs
  • FastAPI(JSON返却):8.63μs
  • Starlette(JSON返却):8.01μs
  • Litestar(JSON返却):8.19μs

ファイルI/O

  • ファイルオープン・クローズ:9.05μs
  • 1KBファイル読み:10.0μs
  • 1KBファイル書き:35.1μs
  • 1MBファイル書き:207μs
  • pickle.dumps():1.30μs
  • pickle.loads():1.44μs

データベース操作

  • SQLite insert(JSON blob):192μs
  • SQLite select by PK:3.57μs
  • diskcache set:23.9μs
  • MongoDB insert_one:119μs
  • MongoDB find_one by _id:121μs

関数呼び出し

  • 空関数呼び出し:22.4ns
  • 引数5つの関数:24.0ns
  • メソッド呼び出し:23.3ns
  • ラムダ呼び出し:19.7ns
  • try/except(例外なし):21.5ns
  • try/except(例外あり):139ns
  • isinstance()チェック:18.3ns

非同期処理

  • コルーチン生成:47.0ns
  • run_until_complete(empty):27.6μs
  • asyncio.sleep(0):39.4μs
  • gather() 10コルーチン:55.0μs
  • create_task() + await:52.8μs
  • async with(コンテキストマネージャ):29.5μs

メモリコスト詳細

  • 文字列
    • 空文字列:41バイト
    • 1文字追加ごとに+1バイト
    • 100文字:141バイト
  • 数値
    • intは28バイト(小・大問わず)
    • 非常に大きなint(10**100):72バイト
    • float:24バイト
  • コレクション
    • リスト、辞書、セットは空でも数十〜数百バイト
    • 1,000要素で数十KBに増加
  • クラスインスタンス
    • __slots__を使うと、通常クラスの約1/3のメモリ消費
    • 大量のインスタンスを扱う場合は__slots__推奨

基本操作・コレクション操作の比較

  • Pythonの演算はC/C++より遅いが、十分高速
  • f-stringが最速の文字列フォーマット
  • リスト内包表記はforループ+appendより26%高速
  • 辞書・セットの検索はリストの200倍高速(1,000件時)
  • len()は最適化不要なレベルの高速

属性アクセス・クラス設計

  • __slots__導入でメモリ大幅削減
  • 属性アクセス速度は通常クラスとほぼ同等
  • namedtupleやdataclassは柔軟性とメモリ効率のバランスが良い

JSON・シリアライズ・Webフレームワーク

  • orjsonやmsgspecは標準jsonより8倍以上高速
  • FastAPIやStarletteはDjango/Flaskより2倍近く高速
  • Webフレームワーク間の違いは、単純なJSON返却のみで測定

ファイルI/O・データベース

  • ファイルI/Oはμsオーダー、1MB書き込みでも0.2ms程度
  • SQLiteやMongoDBの基本操作もμs〜msで完了

まとめ・最適化のヒント

  • パフォーマンスクリティカルな場面では、適切なデータ構造選択が重要
  • 大量のインスタンスを扱う場合、__slots__やnamedtupleの活用が有効
  • Web APIではFastAPIやStarletteのような軽量フレームワークが高効率
  • シリアライズにはorjsonやmsgspecなどの高速ライブラリを検討
  • ベンチマークの数値は絶対値より相対的な差に注目

参考文献・リンク

Hackerたちの意見

逆に言うと、これらの数字を知らなくてもやっていけるなら、Pythonでプログラムを書くべきだよ。これが重要になってくると、Pythonはその仕事に合ったツールじゃなくなる。
その通り。これらの数字が重要なアプリケーションに取り組んでいるなら、Pythonは最適化するには高すぎるレベルの言語だよ。
それか、Pythonのスキャフォールディングを保ちながら、パフォーマンスが重要な部分をCやRustの拡張に押し込むのもありだよ。numpyやpandas、PyTorchなんかがそうしてるしね。でも、君が書いたことの精神には同意するよ。これらの数字は面白いけど、覚える価値はない。むしろ、実際のユーザーデータでどこが遅いのかを見つけるために、プロダクションでコードを計測する方が大事だよ(早すぎる最適化はすべての悪の根源とか言うし)。コードをプロファイリングして(pyspyが一番いいツールだよ、CPUを食うコードを探すのに)、もしPythonでリストに何かを追加するのにどれくらい時間がかかるかを心配しているなら、その操作はPythonでやるべきじゃないよ。
同意だな。Pythonを20年使ってきたけど、これらの数字を知る必要はなかったし、今の仕事でも必要ない。タイトルとは逆にね。パフォーマンス最適化のためにプロファイリングを定期的に使ってるし、必要に応じてCythonやSWIG、JITライブラリ、他のツールを選んでる。これらの数字は、私の意思決定には全く関係ないよ。
なんで?私はturbodbcとpandasを使って、Pythonでかなり大規模な分析データフローを構築したけど、基本的にC++並みに速いよ。メモリを多く使うから君の言う通りだけど、その反面、年間の追加コストは5〜10ドルの話だよ。正直、年間2万ドルかかっても、私みたいな人をもっと雇うよりは安いし、数人でやってBIの人たちに提供するツールを使わせる方がいい。埋め込み作業をする時も、マイクロPythonはエンジニアスタッフにとって扱いやすいんだ。CとPythonの相互運用性が素晴らしいし、Pythonの数字を知ることで、実際にCで何かを作るべきタイミングがわかる。Zigが本当に素晴らしい相互運用性を持ってきてるから、状況は今まで以上に良くなってるよ。君が間違ってるわけじゃないけどね。飛行機を動かすのにPythonは使わないけど、インタープリター言語やGC言語を使ってるからって、リソースに気を使わない理由はないと思うよ。
これらは基本的に最後の手段の数字に見える。プロファイリングして、通常の犯人(大きなディスク読み込み、ネットワークレイテンシー、多項式や指数時間アルゴリズム、無駄に過剰なデータ構造など)を排除した後、個々の操作のレベルで最適化が必要なときに使う数字だね。
空の文字列がどれくらいメモリを使うかを知っても、あんまり得るものはないと思う。この記事や挙げられている数字は、メモリ使用量や具体的な時間測定に異常にこだわってる。プログラマーにとって本当に重要なのは、時間と空間の複雑さだよ。無駄に遅いプログラムやメモリを食うプログラムを設計しないためにね。Pythonを使う前提で、整数が28バイト取ることを知って何の役に立つの?結局、自分が書いたプログラムがパフォーマンス基準を満たしているかどうかを判断しなきゃいけないし、満たしてないなら、もっと賢いアルゴリズムやデータ処理の方法が必要になる。1000x1000のboolの2D配列がどれくらい大きいかを知っても、あんまり役に立たないよ。大事なのは、それが多すぎるのか、もしかしたら大きな整数やビットボードアプローチに切り替えた方がいいのかを知ることだね。あるいは言語を変えるとか。
> Pythonを使う前提で、整数が28バイト取ることを知って何の役に立つの? 大量のオブジェクトを生成する必要がある場合は関係あるよ。これを聞いて、エリック・レイモンドがGCCを移行するためにReposurgeonを使おうとして直面した問題について語っていた投稿を思い出した。http://esr.ibiblio.org/?p=8161
俺は違うと思う。パフォーマンスは常に重要な漏れやすい抽象だよ。それに対する認識は暗黙的か明示的かのどちらか。例えば、リストの追加が線形で、二次的ではなくて、かなり速いってことを知らなかったとしても。単純なプログラムが何らかの理由で必要以上に10000倍遅くても気にしないってことがあったとしても、それが十分なレベルに達しているからとか、問題の非効率が自分に影響しないからとか。自分より下のライブラリの著者はそれを知っているし、あなたが使うAPIや、見るPythonicなコード、LLMSが生成するコードはその漏れやすい抽象の影響を受けるんだ。もしn^2の単純なリスト追加が悪い例だと思うなら、それは違うよ。Pythonの文字列追加はn^2で、それが人々のやり方に影響を与えている。例えば、f文字列は遅延評価だし。Pythonで辞書が速いっていうのは、文字通りどこでも使われる理由でもある。Raymondの古いPycon 2017のトークがこれについて話してる。結局、ブログの著者が提供したのは、パフォーマンスの理解が与える暗黙的な知識に対する数値的な根拠なんだ。
すべてのPythonプログラマーは、低レベルのパフォーマンスの細かいことよりも、もっと重要なことを考えるべきだよ。素晴らしい参考だけど、最適化が必要な稀なケースを除いて、実際にはあんまり関係ないよ。もしワークロードがこのことが実際に重要になるほど増えたら、素晴らしい!それまでは気を散らすだけだね。
そうだね、限界に達したら、Cで実装されたモジュールを探すか、自分で書けばいいよ。これがPythonでいつもやられてきた方法だよ。
同意だな。ただ、これはここ数年ずっと感じてたことなんだ。物事は十分早くて大丈夫だし。このページは、数字を使ってその事実を思い出させてくれるいいリマインダーだね。少なくともしばらくの間は、低レベルのパフォーマンスの細かいことを無視できるって、感じるだけじゃなくて知ることができる。
自分が使っているツールについての一般的な知識は、気を散らすものじゃなくて、むしろ知的な豊かさになるし、特定のケースでは貴重な資産になるよ。
タイトルについてのメタノートだけど、これが多くのコメント者を混乱させているみたいだね:このタイトルは、2012年のジェフ・ディーンの有名な「プログラマーが知っておくべきレイテンシーの数字」のもじりなんだ。文字通りに解釈するためのものじゃないよ。CSの論文や執筆では、過去の論文のテーマをもじったタイトルを書くのが一般的なテーマなんだ。もう一つの一般的な例は「_____は有害と考えられる」っていうタイトルだね。
このタイトルは、数字が実際に役に立つ場合にだけ意味があるよね。今のところ、役に立たないし、意味を成すには数字が多すぎるよ。
"レイテンシーの数字は有害だと考えるのが全て"っていう、すごい論文を書こうと思ってるんだ。それで、学術的な信用が一気に上がるのを見てみたい。
論文の参照についての良い指摘だけど、この著者は最初の段落で本気だってことを示してるよ。コメントしてる人たちは混乱してないと思う。
あのドキュメントは2012年以前のものだね。俺が得た情報によると、基本的にはJeffがGoogleで働き始めた最初の数年に作られたもので、元の検索エンジンのインデックスとサービングに関するものだった。例えば、キャッシュ、RAM、ディスクの比較は、データがRAM(取得に使うインデックス)に保存されるか、ディスク(通常は取得には使われないが、スコアリングに使われる文書)に保存されるかを決定していた。カリフォルニアとオランダの時間の比較もそうで、Googleの最初の国際データセンターはオランダにあったと思うし、インデックス全体を一括でコピーするか、アメリカでバックエンドクエリを処理するかの決定をしなきゃいけなかった。数字は常に古くなっていた。例えば、フラッシュドライブの登場でディスクのレイテンシーが大きく変わった。ある日、Jeffが俺のところに来て「ゲノムデータ用の圧縮アルゴリズムを発明したから、フラッシュから提供できるようにした」って言ってたのを覚えてる(彼は、圧縮されていないゲノムデータに貴重なフラッシュスペースを使うのは無駄だと思ってた)。
> 文字列 > 文字列の目安として、コアの文字列オブジェクトは41バイトかかる。追加の文字は1バイト。これは誤解を招くね。Pythonには3種類の文字列があって(1、2、4バイト/文字)、それぞれ異なるよ。 https://rushter.com/blog/python-strings-and-memory/
ここで多くの人が言ってるのは、Pythonの特定のレイテンシーの数字を気にするなら、別の言語を使った方がいいってこと。でも、俺はそうは思わない。InstagramやDropbox、OpenAIみたいな重要で大規模なコードベースがPythonで育ってきたし、パフォーマンスの問題にぶつかったときに、他の言語に逃げずにPythonの問題を解決する方法を知っておくのはめっちゃ役立つよ。Pythonはすごく便利なツールだし、こういう数字を知っておくことで、ツールの使い方が上手くなるんだ。著者はPython Software Foundationのフェローで、ツールの使い方が本当に上手い。一般的に、Pythonのパフォーマンス問題は言語の限界に達した結果じゃなくて、例えばホットループで無駄にO(10_000)回関数を呼び出すような、だらしない非効率なコードが原因なんだ。もっと具体的な「知っておくべきPythonのレイテンシー数字」をクイズ形式で書いたから、ここで見てみてね https://thundergolfer.com/computers-are-fast
> 重要で大規模なコードベースがPythonで育ってきた どうしてこんなことが起こるんだろう?人々が本質的に型のないインタープリタ言語で大規模なシステムを書くのは、ただの慣性なの?
両方の意見は妥当だと思う。Pythonは遅いから、スピードが重要な場合は避けるべきだけど、時には簡単に避けられないこともあるよね。リスト自体はすごく長ったらしくて、あんまり情報がないと思う。多くの操作がほぼ同じ時間かかるし、整数を足すのが浮動小数点を足すよりほんの少し遅いって、そんなの重要?(これが本当だと思うなら、俺はそうは思わないけど。)要するに「これらのことはだいたい同じ時間がかかる:簡単な計算、関数呼び出し、など。これらはずっと遅い:IO。」って言った方がいいよね。その形での要約はかなり明白だと思う。
うちのビルドシステムはPythonで書かれてるんだけど、クソみたいにならずにPythonのままでいてほしいから、これらの数字はめっちゃ重要なんだ。
クラスをインスタンス化するのにかかる時間が抜けてるね。可読性を向上させるためにコードをリファクタリングしたとき、以前は数マイクロ秒だったものが数十秒かかるようになったのを観察したことがある。元のコードは大きなリストのリストを作っていて、各子リストには4つのフィールドがあって、それぞれ異なるもので、いくつかは整数で、1つは文字列だった。俺は各フィールドの名前とデータを処理するためのヘルパーメソッドを持つ新しいクラスを作った。新しいコードは俺のクラスのインスタンスのリストを作成した。リストの下流の消費者は、どんなデータを受け取っているのかを見るためにクラスを確認できた。現代のPython開発者はこれにデータクラスを使うだろうね。新しいコードはすごく遅かった。著者がクラスをインスタンス化するのにかかる時間を測ってくれたら嬉しいな。
医者に行って、「これをやると痛い」と言ったら、医者が「じゃあそれをやらなきゃいい」と返してきた。編集:ちょっと皮肉な返事だったね。ごめん。でも、なんでクラスやオブジェクトをあちこちで使いたがるのか、考える価値はあると思う。アラン・ケイはオブジェクト指向はメッセージのやり取りだと言ってるし(主にエリクランの人たちが言ってるけど)。リストのリスト(それぞれのリストが4種類の異なるタイプを繰り返す)って、外部関数で操作できて、簡単にシリアライズできるいいデータ構造だと思う。クラスやオブジェクトに変えるのが有用なリファクタリングとは限らないし、進める前にもっと学びたいな。
面白い情報だけど、これはハードな数字じゃないね。141バイトの100文字の文字列情報は正しくないと思う。ASCIIの100文字の文字列にしか当てはまらないからね。UTF-8でエンコードされたユニコード文字列のオーバーヘッドを知る方がもっと役立つと思う。また、100個の絵文字の文字列は441バイト(仮説だけど)で、100個のウムラウト文字の文字列は241バイトかかると思う。
「現代のPythonは明らかに何か間違ってる?」という視点で読むのは面白かったけど、誰もがこれらの数字を「知っているべき」だとは強く反対する。みんながざっくりとしたタイミングを知っておくべきプリミティブが5〜10個くらいあるけど、残りはビッグOアルゴリズムやデータ構造の知識から導き出すべきだと思う。
ここでコメントしてる人たちの多くはポイントを見失ってると思う。パフォーマンスの数字を見るのは重要だよ、PythonでもアセンブリでもHDLでもね。コードが遅い理由がわからなければ、どれくらいのサイクルがかかるかを見て、コードがどう動いてるかを深く理解することを学べる。プログラマーとして成長するにつれて、そういうことが明らかになってくるけど、こういう学びのプロセスを経て、こういう参考資料があれば早く理解できる。パフォーマンスの数字を見て、なぜあるものがすごく時間がかかるのか、あるいは同じ時間がかかるのかを考えるのは、学ぶ絶好の機会だよ。私のPythonキャリアの初期には、ディスク上の重複ファイルを見つけるPythonスクリプトがあったんだけど、最初のバージョンはすごく遅かった。スクリプトを最適化するのにいくつかの試行錯誤があったけど、さまざまなレベルでの最適化の仕方を学んだ。Cを使う必要はなかったよ。キャッシュを使ったり、ディスク上のファイルをすぐに列挙する方法を学んだり、リストの代わりにセットを使ったりした。結果的に、次の実行ではスクリプトが15分から10秒で動くようになった。Cで実装すれば1秒になるかもしれないけど、もしPythonのせいで遅いと思い込んでたら、Cで何時間もかけて15分から14分51秒にしかならなかっただろうね。Cの数字をPythonの数字と並べて見るのが有用だという意見もあるけど、Cを使う代わりにFPGAを使えって言われるのと同じ理由で、Pythonが間違ったツールだと言うのも失礼だと思う。実際、そうじゃないことが多いからね。