You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Gunicorn时Pandas apply函数比Uvicorn慢10倍的原因排查

问题:Gunicorn+UvicornWorker运行Pandas处理比纯Uvicorn慢10倍

我用FastAPI+Uvicorn开发Python API服务,切换为Gunicorn(搭配UvicornWorker)后,处理大型DataFrame的函数速度骤降。例如处理含88000行日期字符串的dummy.csv,将YYYY-MM-DD格式转为YYYY-MM时,使用Pandas的apply()和strftime(),Uvicorn仅需0.23秒,Gunicorn却耗时3.28秒,速度慢了10倍。已知Pandas apply本身效率不算高,但不清楚Gunicorn与Uvicorn为何存在如此大的耗时差异,想了解背后原因及自身可能存在的问题。


代码实现

import time
import pandas as pd
from fastapi import FastAPI

app = FastAPI()


@app.get("/pd")
def panda():
    df = pd.read_csv("./dummy.csv")

    df["date"] = pd.to_datetime(df.date)
    print(df)

    start = time.time()
    df["date"] = df["date"].apply(lambda x: x.strftime("%Y-%m"))
    end = time.time()
    print("[TIME]", end - start)

    print(df)

启动命令

Uvicorn启动

uvicorn main:app

Gunicorn启动

gunicorn -k uvicorn.workers.UvicornWorker main:app --bind 0.0.0.0:8000

依赖包版本

python==3.10.6
fastapi==0.95
pandas==1.4.2
uvicorn==0.19.0
uvloop==0.17.0
gunicorn==20.1.0
...

测试结果

Uvicorn运行耗时

0.23秒

date
0     2010-01-04
1     2010-01-04
...          ...
88286 2019-12-31

[88287 rows x 1 columns]
[TIME] 0.23171710968017578
          date
0      2010-01
...        ...
88286  2019-12

[88287 rows x 1 columns]

Gunicorn运行耗时

3.28秒

date
0     2010-01-04
...          ...
88286 2019-12-31

[88287 rows x 1 columns]
[TIME] 3.2823679447174072
          date
0      2010-01
...        ...
88286  2019-12

[88287 rows x 1 columns]

原因分析与解决方案

1. 进程模型与资源分配差异

  • Uvicorn默认单进程运行,启动时会集中分配资源给单个工作进程,依赖初始化开销仅发生一次;而Gunicorn默认启动与CPU核心数一致的多Worker进程,每个Worker都需要单独初始化Pandas、加载数据,首次请求的初始化开销被放大。如果你的测试是首次请求,这个差异会格外明显。
  • 多进程环境下,CPU、内存资源被多个Worker分摊,Pandas底层依赖的NumPy线程优化会因进程间资源竞争导致效率下降。

2. Uvloop事件循环的使用差异

Uvicorn默认启用uvloop作为事件循环(性能优于标准库事件循环),但Gunicorn+UvicornWorker的组合可能未正确加载uvloop,或多进程环境下uvloop的优势无法充分发挥。

3. Pandas apply的低效性(差异放大诱因)

apply()是Python层面的逐行循环,本身就远慢于Pandas的矢量化操作。单进程环境下,单个进程的资源集中使用掩盖了部分开销;但多进程环境下,多个Worker的Python解释器会竞争GIL(全局解释器锁),进一步降低逐行循环的效率。


解决建议

  • 替换apply为矢量化操作:这是最有效的优化方式,完全规避Python循环开销。将日期格式转换代码修改为:
    df["date"] = df["date"].dt.strftime("%Y-%m")
    
    矢量化操作由Pandas底层C语言实现,速度比apply快数倍,且多进程环境下的性能差异会大幅缩小。
  • 控制Gunicorn Worker数量:如果必须使用多进程,先尝试设置--workers 1,验证是否与Uvicorn速度一致(确认是否为多进程资源竞争导致)。后续根据实际负载调整Worker数量,避免过度竞争。
  • 强制启用uvloop:在Gunicorn启动命令中通过环境变量显式指定事件循环:
    UVICORN_LOOP=uvloop gunicorn -k uvicorn.workers.UvicornWorker main:app --bind 0.0.0.0:8000
    
  • 预热Worker资源:在FastAPI的启动事件中提前完成数据加载或Pandas初始化,避免首次请求的开销:
    @app.on_event("startup")
    def startup_event():
        global df
        df = pd.read_csv("./dummy.csv")
        df["date"] = pd.to_datetime(df.date)
    

内容的提问来源于stack exchange,提问作者Wapar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 13:20:35