2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python3.14tのFree-Threading x FastAPIを検証してみる

Last updated at Posted at 2025-10-30

今月のPython 3.14リリースに伴い、遂にFree-Threadingが正式サポートされました。
今回はFastAPIでその恩恵を受けることが可能なのか、検証していきたいと思います。

Free Threadingはまだオプトインの機能であり、uvなどでは「3.14t」をインストールすることで正式にGILが無効化された版がインストールされます。

GILとは

GIL(Global Interpreter Lock)とは排他制御機構のことで、複数のスレッドが同時に実行されることを防ぎます。Rubyなどでも採用されています。

FastAPI x uvicornでの同期/非同期 処理

FastAPIが内部で利用するASGI FrameworkであるStarletteは通常とは異なる以下のような挙動をします。

  • async defエンドポイント
    • イベントループ内で実行(uvloop、asyncio event loop)
  • def エンドポイント
    • 外部スレッドプールで実行
      • デフォルトでは外部スレッドプールのサイズは上限は40

ここで、以下のようなコードを準備して、python 3.14と3.14t(Free Threading)版でそれぞれ実行してみます。

  • 2025年10月31日現在、httptoolのFree Threading対応版がリリースされていない?ためにmasterブランチの内容を取ってきています
  • uvを用いる場合、uv自身のversionを最新版に更新する必要があるかもしれません
import asyncio
import sys
import time

from fastapi import FastAPI


app = FastAPI()

@app.get("/")
def read_root():
    return {"message": f"Hello, World@python{sys.version_info.major}.{sys.version_info.minor}"}

@app.get("/sync_sleep")
def sync_sleep():
    time.sleep(3)
    return "Hello, World from sync endpoint!"

@app.get("/async_sleep")
async def async_sleep():
    await asyncio.sleep(3)
    return "Hello, World from async endpoint!"

@app.get("/sync_cpu")
def sync_cpu():
    count = 0
    for i in range(10**7):
        count += i
    return f"Sync CPU-bound result: {count}"

@app.get("/async_cpu")
async def async_cpu():
    count = 0
    for i in range(10**7):
        count += i
    return f"Async CPU-bound result: {count}"

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8050)

エンドポイントは以下です。

  • /sync_sleep
    • time.sleep()による同期IOバウンド処理
  • /async_sleep
    • asyncio.sleep()による非同期IOバウンド処理
  • /sync_cpu
    • 同期CPUバウンド処理
  • /async_cpu
    • 非同期CPUバウンド処理

time.sleepはCPUバウンド処理ではなくGILに影響されません

検証

Vegetaを使って負荷試験的なことをします。

Vegetaの設定項目

  • duration: 10s
  • max workers: 5
  • rate 10

結果

結果を以下に示します。
それぞれの値は、 Latencyのmin, mean, 50 Percentile, 90 Percentile, max となっています。

Version 3.14 3.14t (Free Threading)
sync_sleep 3.00s, 3.01s, 3.01s, 3.01s, 3.05s 3.00s, 3.01s, 3.01s, 3.01s, 3.03s
async_sleep 3.00s, 3.00s, 3.00s, 3.00s, 3.01s 3.00s, 3.00s, 3.00s, 3.01s, 3.01s
sync_cpu 366ms, 1.04s, 1.05s, 1.22s, 1.28s 199ms, 205ms, 203ms, 209ms, 237ms
async_cpu 226ms, 1.00s, 1.06s, 1.09s, 1.32s 214ms, 957ms, 1.01s, 1.03s, 1.21s

/sync_cpuのCPUバウンドな処理においては、GILの有無によって速度に大きな差が出ていることがわかります。

まとめ

sync_sleep, async_sleepに関しては、GILが影響しないため同期関数でも非同期関数もLatencyは大きく変わりません。
ただ、CPUバウンドであるsync_cpu、async_cpuに関しては、

  • sync_cpu: GILの影響を受けない3.14tが大幅に速い
  • async_cpu: IOバウンドな処理がないため、単一のリクエストが処理を占有し処理速度に変化なし

という結果になりました。

ただsync_**に関しては、外部スレッドプールの上限サイズが40という設定を変更せずに実行していることに留意してください。

Vegetaの実行結果


$ echo 'GET http://localhost:8050/sync_sleep' | vegeta attack -duration=10s -max-workers=5 -rate=10 | tee results.bin | vegeta report

Requests      [total, rate, throughput]         21, 1.74, 1.39
Duration      [total, attack, wait]             15.069s, 12.065s, 3.003s
Latencies     [min, mean, 50, 90, 95, 99, max]  3.002s, 3.008s, 3.007s, 3.008s, 3.026s, 3.047s, 3.047s
Bytes In      [total, mean]                     714, 34.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:21  
Error Set:
$ echo 'GET http://localhost:8050/async_sleep' | vegeta attack -duration=10s -max-workers=5 -rate=10 | tee results.bin | vegeta report

Requests      [total, rate, throughput]         21, 1.75, 1.40
Duration      [total, attack, wait]             15.022s, 12.02s, 3.003s
Latencies     [min, mean, 50, 90, 95, 99, max]  3.001s, 3.003s, 3.003s, 3.004s, 3.007s, 3.01s, 3.01s
Bytes In      [total, mean]                     735, 35.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:21  
Error Set:
$ echo 'GET http://localhost:8051/sync_sleep' | vegeta attack -duration=10s -max-workers=5 -rate=10 | tee results.bin | vegeta report

Requests      [total, rate, throughput]         21, 1.74, 1.40
Duration      [total, attack, wait]             15.049s, 12.043s, 3.006s
Latencies     [min, mean, 50, 90, 95, 99, max]  3.003s, 3.007s, 3.007s, 3.01s, 3.018s, 3.026s, 3.026s
Bytes In      [total, mean]                     714, 34.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:21  
Error Set:
$ echo 'GET http://localhost:8051/async_sleep' | vegeta attack -duration=10s -max-workers=5 -rate=10 | tee results.bin | vegeta report

Requests      [total, rate, throughput]         21, 1.75, 1.40
Duration      [total, attack, wait]             15.019s, 12.015s, 3.004s
Latencies     [min, mean, 50, 90, 95, 99, max]  3.001s, 3.004s, 3.003s, 3.007s, 3.009s, 3.012s, 3.012s
Bytes In      [total, mean]                     735, 35.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:21  
Error Set:
$ echo 'GET http://localhost:8050/sync_cpu' | vegeta attack -duration=10s -max-workers=5 -rate=10 | tee results.bin | vegeta report

Requests      [total, rate, throughput]         50, 4.90, 4.63
Duration      [total, attack, wait]             10.788s, 10.213s, 574.996ms
Latencies     [min, mean, 50, 90, 95, 99, max]  366.429ms, 1.041s, 1.053s, 1.217s, 1.251s, 1.284s, 1.284s
Bytes In      [total, mean]                     1950, 39.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:50  
Error Set:
$ echo 'GET http://localhost:8050/async_cpu' | vegeta attack -duration=10s -max-workers=5 -rate=10 | tee results.bin | vegeta report

Requests      [total, rate, throughput]         52, 4.99, 4.71
Duration      [total, attack, wait]             11.052s, 10.417s, 634.503ms
Latencies     [min, mean, 50, 90, 95, 99, max]  226.382ms, 1.002s, 1.058s, 1.085s, 1.088s, 1.315s, 1.32s
Bytes In      [total, mean]                     2080, 40.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:52  
Error Set:
$ echo 'GET http://localhost:8051/sync_cpu' | vegeta attack -duration=10s -max-workers=5 -rate=10 | tee results.bin | vegeta report

Requests      [total, rate, throughput]         100, 10.10, 9.90
Duration      [total, attack, wait]             10.099s, 9.9s, 198.702ms
Latencies     [min, mean, 50, 90, 95, 99, max]  198.702ms, 204.989ms, 202.967ms, 208.587ms, 213.43ms, 236.542ms, 237.451ms
Bytes In      [total, mean]                     3900, 39.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:100  
Error Set:
$ echo 'GET http://localhost:8051/async_cpu' | vegeta attack -duration=10s -max-workers=5 -rate=10 | tee results.bin | vegeta report

Requests      [total, rate, throughput]         55, 5.34, 4.95
Duration      [total, attack, wait]             11.105s, 10.297s, 807.996ms
Latencies     [min, mean, 50, 90, 95, 99, max]  214.247ms, 956.982ms, 1.007s, 1.034s, 1.036s, 1.197s, 1.205s
Bytes In      [total, mean]                     2200, 40.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:55  
Error Set:
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?