Python多线程调用API性能下降原因及优化方案咨询
我有三个逻辑完全一致的Flask API(API1.py、API2.py、API3.py),仅端口号和加载的PyTorch模型文件路径不同:
- 顺序调用API1三次:单请求平均耗时约0.84秒,总耗时4.1秒;
- 用
ThreadPoolExecutor多线程同时调用三个API:单请求耗时增至3.99-4.61秒,总耗时达4.73秒。
请问为何多线程调用反而导致执行时间增加?如何有效降低执行时间?
Flask API示例代码(API1.py)
from flask import Flask, request, jsonify import torch import random import time loaded_mdl = torch.load("/folder1/Tuned_Model_2.bin") import spacy nlp = spacy.load("en_core_web_sm") import resource soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard)) app=Flask(__name__) @app.route('/get_ans',methods=['GET','POST']) def get_ans(): quest = request.json question = quest['question'] context = quest['context'] try: query = str(question) context = str(context) to_predict = [{ "context": context, "qas": [{ "question":query, "id": random.randint(1, 10000), }], }] try: start_predict_time = time.time() answers, _ = loaded_mdl.predict(to_predict) end_predict_time = time.time()- start_predict_time except Exception as e: # 自定义异常处理逻辑 pass doc = nlp(context) ans_text = "" for sent in doc.sents: # 基于answers的处理逻辑 ans_text = processed_answer # 假设processed_answer是处理后的结果 return str(end_predict_time)+"@#$"+ans_text except Exception as e: return "error" if __name__ == "__main__": app.run(host='host1',port=port_1,threaded = True)
注:原代码补充了缺失的random、time导入,修正了已废弃的random.rand()方法,补充部分占位逻辑保证代码完整性。
顺序调用代码
import requests import time URL = 'http://host:port_1/get_ans' json_ = {"question": "示例问题", "context": "示例上下文"} def fetch(session, url): with session.post(url,json=json_) as response: print(response.text) def main(): with requests.Session() as session: for _ in range(3): fetch(session, URL) start_time = time.time() main() print(f"Total time - {time.time() - start_time}")
多线程调用代码
from concurrent.futures import ThreadPoolExecutor import requests import time URL1 = 'http://host:port_1/get_ans' URL2 = 'http://host:port_2/get_ans' URL3 = 'http://host:port_3/get_ans' json_ = {"question": "示例问题", "context": "示例上下文"} def fetch(session, url): with session.post(url,json=json_) as response: print(response.text) def main(): with ThreadPoolExecutor(max_workers=3) as executor: with requests.Session() as session: executor.map(fetch, [session] * 3, [URL1,URL2,URL3] ) start_time = time.time() main() print(f"Total time - {time.time() - start_time}")
一、多线程调用变慢的核心原因
CPU资源严重竞争
PyTorch默认会用多线程执行CPU运算(比如矩阵乘法),每个API的模型预测都会抢占大量CPU核心;加上spaCy的模型处理也是CPU密集型任务,三个API同时运行时,CPU核心被瓜分,每个任务的实际可用算力大幅下降,导致单请求耗时飙升。内存带宽瓶颈
三个模型同时加载到内存并进行推理时,会频繁读取模型参数,导致内存带宽饱和,数据读取延迟增加,拖慢整体执行速度。Flask内置服务器局限性
Flask默认的Werkzeug服务器是单进程多线程模型,并不适合高并发CPU密集型任务。多请求同时到达时,线程切换开销加上CPU资源不足,会让请求处理效率急剧降低。
二、有效优化方案
限制PyTorch CPU线程数
在每个API代码开头添加以下配置,避免单个API占用过多核心:torch.set_num_threads(1) torch.set_num_interop_threads(1)让每个API仅使用1个CPU核心,三个API可以并行利用3个核心,减少资源竞争。
替换生产级服务器
用Gunicorn或Uvicorn替代Werkzeug,采用多进程模式启动每个API:# 示例:用Gunicorn启动API1,单进程单线程 gunicorn --workers 1 --threads 1 API1:app -b host1:port_1独立进程可以更好地利用多核CPU,避免进程内线程竞争。
批量处理请求
合并多个请求为批量请求发送给单个API,利用模型的批量推理能力(通常批量推理的单样本耗时更低),减少多API并发的资源消耗。GPU加速推理
如果条件允许,将模型部署到GPU上:PyTorch的GPU推理能大幅降低单请求耗时,且GPU的并行处理能力更适合多并发场景,从根源解决CPU资源竞争问题。调整调用端线程池配置
根据服务器CPU核心数调整max_workers,避免同时发起超出CPU承载能力的请求。比如CPU有4核,max_workers设置为3或4即可。
内容的提问来源于stack exchange,提问作者usr_lal123

