地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【Python】io_uringの対応状況を調べる

この記事は「渡部 Advent Calendar 2025」の8日目の記事です。


bugs.python.org

io_uringって?

Linux カーネルが提供する 超高速・低オーバーヘッドな非同期 I/O インターフェイス です。2019 年に Linux 5.1 の一部として導入され、従来の epoll、aio、read/write システムコールよりも高速に I/O を処理できます。

man7.org

io_uring は何を解決するのか

io_uring が提供するのは、次のような「I/O パスの改革」です。

特に「システムコール削減」のインパクトは大きく、CPU の文脈切り替えを減らすことで、極めて低いレイテンシを実現します。

私も過去に色々言及していたりするのでその辺も合わせて見てもらえると嬉しいです。

ryuichi1208.hateblo.jp

Pythonでの対応状況

そんなio_uringですがPython におけるのサポート状況は「可能ではあるが、まだ発展途上かつ限定的」というのが現状のようでした。以下に調べた際の内容を記載しておきます。

liburing をラップする Python バインディング/ラッパー

例えば Liburing というプロジェクトがあって、これは C の liburing を Cython + Python バインディング でラップしています。 これを使えば、Python から比較的直接に io_uring の機能を呼び出すことができ、「低レイヤーな非同期 I/O」を試せます。

github.com

io_uring を使った Python 用ライブラリ/実験的な async ランタイム

Python 組込みの標準 I/O モデル(たとえば asyncio)は、ファイル I/O に対しては本来の非同期を提供していないため、従来はスレッドプールに頼るのが一般的でした。そこで io_uring を使って「真の非同期ファイル I/O」を Python で実現しようという試みがあり、少数ながら実装・実験があります。たとえ「Exploring io_uring with Python」のようなドキュメントも存在します。

dtornow225.substack.com

Python を使って、io_uring の基本的なシミュレーションを行う簡単なコードを紹介しています。これは本物の io_uring の完全な実装ではなく、概念を理解するための簡略化されたものです。以下のコードでは、Submission Queue Entry(SQE) と Completion Queue Entry(CQE) の管理を行います。

from dataclasses import dataclass
from typing import Any, Optional

@dataclass
class SQE:
  code: int
  data: Any
  user_data: Any  # ユーザーデータ

@dataclass
class CQE:
  data: Any
  user_data: Any  # SQE と対応するユーザーデータ

class IOUring:
  def __init__(self, queue_size=32):
    self.submission_queue = []
    self.completion_queue = []
    self.queue_size = queue_size

  def enqueueSQE(self, sqe: SQE):
    if len(self.submission_queue) < self.queue_size:
      self.submission_queue.append(sqe)
      return True
    return False

  def enqueueCQE(self, cqe: CQE):
    self.completion_queue.append(cqe)

  def dequeueSQE(self) -> Optional[SQE]:
    if self.submission_queue:
      return self.submission_queue.pop(0)
    return None

  def dequeueCQE(self) -> Optional[CQE]:
    if self.completion_queue:
      return self.completion_queue.pop(0)
    return None

  def step(self):
    sqe = self.dequeueSQE()
    if sqe:
      # I/O 操作をシミュレート
      cqe = CQE(data=f"Processed: {sqe.data}", user_data=sqe.user_data)
      self.enqueueCQE(cqe)
      return True
    return False

Pythonはすでにaiofilesがあるじゃん

github.com

というのがあります。これで完全に非同期サポートしているかと思ったのですがこれは実はaiofiles 自身のドキュメントにあるように、 aiofiles は “delegating operations to a separate thread pool” を使っており、ローカルファイル I/O をスレッドプールにオフロードする方式です。

つまり、Python のコードからは await f.read() のように await 可能だが、実際には Python 標準の blocking read()/write() を別スレッドで実行しているだけ であり、OS やファイルシステムにおける non‑blocking ファイル I/O(例えば Linux の io_uring/AIO、Windows の Overlapped I/O)とは別物。実際、標準の非同期 I/O ライブラリである asyncio のドキュメントにおいても、「ファイル I/O は基本的に blocking」であり、OS による非同期ファイル I/O が広くサポートされていないために asyncio 自体はファイル用のネイティブ async I/O を提供していない、という注意書きがあります。

→ つまり、aiofiles の “async file I/O” は「Python コードから見た非同期感」= 「メインスレッドをブロックせずにファイル操作を待てる」 という意味であって、 「カーネル↔ユーザ領域の非同期 I/O パス(zero syscall, shared‑memory queue など)」を使った true non‑blocking I/O ではありません。

私もaiofiles使っておけばいいじゃんくらいでずっと思っていたのでちょっとびっくりでした。ちなみにio_uring関連のissueは定期的に作成されてはcloseされているように見えます。

github.com

まとめ

現時点ではPytonではio_uringを完全にサポートしているわけではなくサードパーティバインディングなどを使って使用する必要があります。ファイルIOがブロッキング操作で困るケースはasyncioを使ったアプリケーションだとある気がするので現在はスレッド生成でなんとかなっているのですが真の非同期IOについては今後に期待です。