Communityコーディング&開発github.com

magmalake/mojo-data-performance

高速なMojoデータコードを作成し、その実行時間がどこに費やされているかを、パイプラインの行単位ループからリーダー、ライター、コーデックに至るまで特定します。また、検証可能なベンチマーク結果を取得します。Arrowレコードバッチ、Parquet、Icebergスキャン(arrow-mlake、parquet.mojo、iceberg.mojo、dista、orca)をループするMojoコードの作成やレビュー時、パーサー・リーダー・ライター・エンコーダー・コーデックの高速化時、ベンチマーク作成時、pyarrowやparquet-cpp、arrow-rs、Rayとの比較時、数値が良すぎる場合や実行間で変動する場合、高速化を主張する前に使用します。Modularのmojo-syntaxスキルと併用します。

mojo-data-performance とは?

このスキルは、Mojoで記述されたデータパイプラインのパフォーマンスを詳細に分析し最適化するためのガイドです。Arrow、Parquet、Icebergなどのデータソースを扱う際、パイプラインのボトルネックが「デコード」「行単位の処理」「待機(ストレージ/ロック/スレッド)」のどこにあるかを特定する方法を提供します。具体的には、スキャン単体の時間をベースラインとして計測し、その上で行単位の処理時間を比較することで、最適化すべき箇所を明確にします。さらに、行ループ内のコーディングルール(ポインタの直接参照、キャッシュラインを意識したデータ構造、分岐の削減など)や、パイプライン全体のルール(スレッド競合の回避、非同期I/O、共有ライブラリの管理)、リーダー・ライター・コーデックの最適化手法について、実測値に基づいた具体的なルールと効果を提供します。また、信頼できるベンチマーク数値を取得するための方法や、高速化を主張する前に確認すべきポイントも含まれています。

対応~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/magmalake/skills/tree/HEAD/mojo-data-performance

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

データパイプラインの時間は、以下の3つに費やされます。データの読み取りとデコード、各行に対する処理、そして待機(ストレージ、ロック、またはビジーなスレッドの待機)です。デコードは通常、ライブラリ側でチューニング済みです。各行に対する処理はあなたが書く部分であり、不注意なループは2~3倍のコスト増につながります。

このファイルの大部分は、スキャンのバッチを消費するコードに関するものです。その基盤となるリーダー、ライター、コーデック、および信頼できる数値を取得する方法については、最後の2つのセクションとその参考文献を参照してください。

まず、時間の計測

比較対象となる別の実装(Ray、DuckDB、pyarrow)があることは稀です。代わりに、スキャン自体を基準として使用します。

  1. スキャン単体の時間を計測する。 同じスキャンを、同じカラム、フィルター、ScanOptions で実行し、バッチを破棄します。これがデコードのみのベースラインです。この時間より速いパイプラインはありえません。
  2. 処理単体の時間を計測する。 バッチごとの呼び出し(バッチを消費する fold、add など)を perf_counter_ns() でラップし、時間を合計します。
  3. 比較する。 1スレッドの場合、合計時間がデコードのみの時間の約1.5倍以内であれば、処理は軽量です。それを超える場合、処理がボトルネックであり、最適化の価値があります。多くのスレッドの場合、処理が1スレッドで実行されている一方で、デコードが多くのスレッドで実行されていないかも確認します。

測定例:7700万件のトリップに対するグループ化が1スレッドで4.6秒かかり、そのうち行単位の処理が2.1秒(45%)でした。8スレッドでは2.6秒かかり、同じ処理が2.1秒(82%)を占めました。これは、1つのスレッドがすべてのバッチを消費したためです。references/profiling.md に手順と落とし穴があります。

行ループのルール

各ルールには、distaシャッフルベンチマーク(7700万行、ローカルテーブル、1スレッド)で節約できた時間を記載します。コードは references/hot-loops.md にあります。

ルール測定効果
各カラムの生ポインタをバッチごとに一度取得し、その後はポインタをインデックスする。ループ内で List や Tuple[List, List] をインデックスしない。行単位の処理全体が1.40秒→0.78秒
1つのキーが蓄積するすべての要素を、並列配列ではなく、1つのスロット(構造体配列)に並べて保持する。これにより、1回の更新で1つのキャッシュラインにアクセスする。次のルールと合わせて、2.09秒→1.40秒
分岐なしでフィルタリングする:すべての行を計算し、kept インデックスに書き込み、Int(keep) だけ kept を進める。その後、保持された行を別のタイトなループで追加する。上記ルールと同時に測定
キーが小さな整数の場合(ゾーン×ゾーン×時間で170万通り)、Dict の代わりに密な配列をキーでインデックスする。単独では未測定。両方のバージョンで密な配列を使用。
最適化中に浮動小数点加算の順序を変更しない。そうすれば、答えがビット単位で古いものと一致する必要があり、無料の正確性チェックとなる。上記のすべてのステップでビット単位の一致を確認。

パイプライン全体のルール

ルール測定効果
1つのスレッドが、多くのスレッドでデコードされたものを消費しないようにする。各ワーカーに独自のアキュムレータを持たせ、最後にマージする(distaのマップタスクと同様)。16スレッドで、1プロセスのグループ化がAWS上で202秒から126秒に短縮。
デコードスレッドが1つであっても、複数の読み取りを並行して実行する:iceberg.mojo の ScanOptions.files_at_once(および overlap_io)。S3からの読み取りで、1スレッドの場合に6.6~6.9倍の高速化。
共有Cライブラリはプロセスごとに一度ロードし、ハンドルを保持する。決して呼び出しごとにロードしない。zstdを呼び出しごとにオープンした場合、スキャン時間の52%が dyld に費やされた。修正後、2687ms→262ms。
バイト単位で繰り返されるパース結果(マニフェスト内のAvroスキーマなど)はキャッシュする。マニフェスト計画の固定コストが3.6倍削減(113.7μs→31.9μs)。
レコードごとにアロケートしない。再利用可能なスロットにデコードする。Avroリーダーは、カーソル移動前にレコード1件あたり約60回アロケートしていた。
上限を認識する:数コアを超えるとスキャンはメモリ帯域幅に制限され、それ以上スレッドを増やしても効果はない。代わりに、読み取るバイト数を減らす。Icebergスキャンは1コアで35.8ms、4コアで12.1ms(約3倍)。

references/case-studies.md にこれらの各事例と、問題の兆候が説明されています。

リーダー、ライター、コーデック

デコード自体が遅い部分である場合、ルールのほとんどは、後で破棄される作業を行わないことに関するものです。詳細と数値は references/readers-and-codecs.md にあります。

  • 各高速パスのゲート条件が、そこに到達する呼び出し元によって満たされることを確認し、計装によってそれが実際に実行されていることを証明する。 どのリストカラムも満たせないゲートを持つベクトル化パスにより、1つの9KiBカラムの読み取りが43%の効率に留まりました。
  • 後で破棄される可能性が高い計算は行わない。 プレーンエンコーディングを選択するためだけに辞書を構築することは、int64カラム書き込みの67%を占めていました。これを削除することで、バイト単位で同一の出力に対して書き込みが3.3倍高速化しました。
  • コピーではなく借用する。 定義レベルを有効性ビットマップに直接デコードするなど、最終レイアウトに直接デコードします。
  • データに連続性がある場合は、その連続部分で処理する。 ヌル率1%の場合、約250回の memmove で、約25,000回のスロットごとの分岐処理を置き換えられます。
  • ステージがすでにメモリ帯域幅に達しているタイミングを認識する。 18.3MBを1.2ms(約30GB/s)で入出力するステージは、スキップする以外に方法はありませんでした。
  • スレッドを追加する前に、最も遅い項目を見つける。 カラムチャンクで分割した場合、1つの大きなカラムが常に実行中だったため、読み取りは1.39倍に制限されました。

最適化自体と結果の両方をテストします。意図的に最適化を破壊し、テストが失敗することを確認します(references/testing-optimizations.md)。Mojo固有の落とし穴(最後の使用で値が消滅する MutUntrackedOrigin など)については、references/mojo-hazards.md を参照してください。

信頼できる数値の取得

references/measurement.md に完全なルールがあります。magmalakeで結論を変えたルールは以下の通りです。

  • 平均値のみではなく、中央値(p50)とp90を報告する。 1回の遅い最初の呼び出しにより、平均値は30%変動したが、p50はまったく変動しませんでした。
  • ベンチマークは事前にビルドし、mojo run で時間を計測しない。 JITにより、あるベンチマークが1.9倍に膨れ上がりました。
  • マシンの状態が一定であることを確認する。 同じベンチマークが、アイドル時は31ms、16のビジースレッドと同時実行時は89msかかりました。
  • 参照元のAPIとスレッド数を明記する。 pq.read_table はparquet-cppの読み取りパスではなくpyarrowのデータセットスキャナーであり、pyarrowのスレッド設定により、あるファイルの読み取り時間が2.9倍変動しました。
  • 2つの数値が一致しない場合、違いを説明する前に、それらが同じ方法で生成されたかを確認する。

高速化を主張する前に

  • 高速化の前後で結果を比較する。 可能な限り、正確に一致することを確認します(最後のループルールを参照)。間違った高速な答えは、遅くても正しい答えよりも悪いです。
  • 両方の数値と設定を報告する。 行数、スレッド数、ローカルディスクかS3か、キャッシュがウォームかどうか。
  • 残っている問題を説明する。 シャッフル修正後も、777msのうち325msは依然としてカラムのコピーに費やされており、多くのスレッドの場合、処理は依然として1スレッドで実行されています。

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

magmalake/dista

Write and run distributed Mojo programs with dista, magmalake's Ray-style task framework, where one compiled binary is the driver, the head and every worker. Use when writing or reviewing Mojo code that imports dista or mentions distributed Mojo, a remote task, `Task`/`Task2`/`TaskMany`/`TaskWith`, `ObjectRef`, `rt.remote`, `rt.get`, `Seq`, an actor with methods or checkpoints, a cluster, a head, workers, `DISTA_ROLE`/`DISTA_HEAD`, the `dista` command (status, drain, scale, timeline, stop), draining, autoscaling, or a dista runtime inside an orca pipeline step; and when a dista job hangs, is refused with a registry mismatch, or fails with a lost result. Pairs with Modular's mojo-syntax skill and magmalake's mojo-data-performance skill.

magmalake/magmalake-apps

Build a data application on magmalake as one Mojo project, with pipelines run by orca, a lake of Iceberg tables over S3, and a long-running flare web app reading them, then deploy it to AWS with `orca aws`. Use when starting a new magmalake project or laying one out (pixi, mojoshelf, `bin/`, `pipelines/`, `src/`); when a pipeline step writes Iceberg tables that an app reads; when writing a flare web app, console or dashboard over the lake; when running the project locally with Postgres and a local warehouse; or when deploying to AWS (`orca aws up`, `push`, `register`, `app`, `status`, `down`), setting up its hostnames, secrets, public bucket or costs. Pipelines in depth (partitions, keyed, sensors, dista tasks) are the `orca` skill's.

関連スキル