前言
这个一个项目上遇到的问题,我有一个很大的人脸识别模型,每一次Http request请求,这个模型就会像以前一样在一次会话中执行一次模型加载。在以前的应用场景下,要么是模型较小,根本感受不到加载;要么就是根本就没有考虑过速度。但这次需要提供一个网页的服务,很明显得发现,响应速度很慢,经过分析发现,80%的时间都消耗在模型加载上了。
因此提出两个疑问:
- 能不能不要重复加载模型呢?
- 模型加载很耗时,能不能再开启服务的时候就完成加载呢?
思路
经过讨论后发现,这两个疑问有合理的解决方案,当然我这个方法不一定是最为合理和有效的,欢迎大家批评指正。
流程如下:
Queue线程等待避免重复加载
这篇Python线程教程给了我一定的思路,我可以使用queue库中的队列完成。原文说到:
从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用put() 和 get() 操作来向队列中添加或者删除元素。
所以改写之前模型加载,整理思路就是:使用线程的方式,在图片服务初始化后就完成加载,然后对queue进行监听,一旦queue队列传入了数据,while True循环的阻塞就解除,继续执行下面的代码。完成解析之后,将结果放入结果队列进行值返回(因为不能直接return),如此循环等待。
class detectFace_service(Thread):
def __init__(self, img_queue, result_queue):
super().__init__()
self.img_queue = img_queue
self.result_queue = result_queue
self.sess = tf.Session()
self.graph = tf.get_default_graph()
# self.graph.as_default()
facenet.load_model_sess(model, self.sess)
def run(self):
while True:
# 从共享队列中获取传入的信息
img_url = self.img_queue.get()
print("传来了一张照片" + img_url)
# 传入结果队列中
result = detectFace2(self.sess, self.graph, img_url)
self.result_queue.put(result)
因为涉及到tf的特性,session和graph需要传入,当然这也要看你的函数是怎么写的了。
然后启动线程函数就可以像下面这样写了,传入全局的队列变量。
def start_img_service():
img_thread = detectFace_service(img_queue=IMG_QUEUE, result_queue=RESULT_QUEUE)
print("图片服务线程启动")
img_thread.start()
接下来,就按照Django项目正常提供网页服务,这部分就不介绍了,只写一下启动对应的函数,只需要将图片路径丢到全局队列就行了。
def gerUserImgName2(img_path):
print("触发了人脸函数")
IMG_QUEUE.put(img_path)
print("开始等待处理")
result = RESULT_QUEUE.get()
print("结果是:", result)
return result
项目启动时开启线程
讨论1、讨论2
上面两个链接的讨论也挺不错的,给了不少思路。
我们避免了模型的重复加载之后,就需要将这个服务在项目启动时开启,这样模型就能提前加载好,完成加速。
这里我放入urls.py
中,而不是model.py
中,这是Django的机制,我还不是很懂,等有空研究了来这边补充,留个TODO,也欢迎大家提出。放入urls.py
可以直接启动时加载,model.py
发现会重复加载两次,显然消耗了内存。
直接将上面的start_img_service
写在urls.py
中,然后运行。
def start_img_service():
img_thread = detectFace_service(img_queue=IMG_QUEUE, result_queue=RESULT_QUEUE)
print("图片服务线程启动")
img_thread.start()
start_img_service()
系统就会在启动时自动运行
模型预热
由这篇文章得到的启示。
在处理图片的时候还发现一个问题,在上传第一张图片进行处理的时候,大概是因为函数还没加载到内存里面,第一张处理得速度很慢,但是第二张的速度有显著提升,大概从10s提升到2s左右。
因此,开机启动时直接处理一张图片,完成模型的预热。
def start_img_service():
img_thread = detectFace_service(img_queue=IMG_QUEUE, result_queue=RESULT_QUEUE)
print("图片服务线程启动")
img_thread.start()
start_img_service()
IMG_QUEUE.put(os.path.join(settings.MEDIA_ROOT,"WechatIMG760.jpg"))