From 4948397107785831e58a183b3987d328834481a5 Mon Sep 17 00:00:00 2001 From: ShiningZhang Date: Mon, 9 May 2022 16:02:58 +0800 Subject: [PATCH] update offical doc --- .../6-8_C++_Server_Preformance_Tuning_CN.md | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 doc/Offical_Docs/6-8_C++_Server_Preformance_Tuning_CN.md diff --git a/doc/Offical_Docs/6-8_C++_Server_Preformance_Tuning_CN.md b/doc/Offical_Docs/6-8_C++_Server_Preformance_Tuning_CN.md new file mode 100644 index 00000000..2b72bc72 --- /dev/null +++ b/doc/Offical_Docs/6-8_C++_Server_Preformance_Tuning_CN.md @@ -0,0 +1,75 @@ +# C++ Serving性能分析与优化 + +## 背景知识介绍 +1) 首先,应确保您知道C++ Serving常用的一些[功能特点](./Introduction_CN.md)和[C++ Serving 参数配置和启动的详细说明](../Serving_Configure_CN.md)。 +2) 关于C++ Serving框架本身的性能分析和介绍,请参考[C++ Serving框架性能测试](./Frame_Performance_CN.md)。 +3) 您需要对您使用的模型、机器环境、需要部署上线的业务有一些了解,例如,您使用CPU还是GPU进行预测;是否可以开启TRT进行加速;你的机器CPU是多少core的;您的业务包含几个模型;每个模型的输入和输出需要做些什么处理;您业务的最大线上流量是多少;您的模型支持的最大输入batch是多少等等. + +# 2.Server线程数 + +首先,Server端线程数N并不是越大越好。众所周知,线程的切换涉及到用户空间和内核空间的切换,有一定的开销,当您的core数=1,而线程数为100000时,线程的频繁切换将带来不可忽视的性能开销。 + +在BRPC框架中,用户态协程worker数M >> 线程数N,用户态协程worker会工作在任意一个线程中,当RPC网络传输IO操作让出CPU资源时,BRPC会进行用户态协程worker的切换从而提高RPC框架的并发性。所以,极端情况下,若您的代码中除RPC通信外,没有阻塞线程的任何IO或网络操作,您的线程数完全可以 == 机器core数量,您不必担心N个线程都在进行RPC网络IO,而导致CPU利用率不高的问题。 + +Server端**线程数N**的设置需要结合三个因素来综合考虑: + +## 2.1 最大并发请求量M + +根据最大并发请求量来设置Server端线程数N,根据[C++ Serving框架性能测试](./Frame_Performance_CN.md)中的数据来看,此时**线程数N应等于或略小于最大并发请求量M**,此时平均处理时延最小。 + +这也很容易理解,举个极端的例子,如果您每次只有1个请求,那此时Server端线程数设置1是最合理的,因为此时没有任何线程切换的开销。如果您设置线程数为任何大于1的数,必然就带来了线程切换的开销。 + +## 2.2 机器core数量C + +根据机器core数量来设置Server端线程数N,众所周知,线程是CPU core调度执行的最小单元,若要在一个进程内充分使用所有的core,**线程数至少应该>=机器core数量C**,但具体线程数N/机器core数量C = ?需要您根据您的代码中网络、IO、内存和计算所占用的比例来决定,一般用户可以通过设置不同的线程数来测试CPU占用率来不断调整。 + +## 2.3 模型预测时间长短T + +当您使用CPU进行预测时,预测阶段的计算是使用CPU完成的,此时,请参考前两者来进行设置线程数。 + +当您使用GPU进行预测时,情况有些不同,此时预测阶段的计算是由GPU完成的,此时CPU资源是空闲的,而预测操作是阻塞该线程的,类似于Sleep操作,此时若您的线程数==机器core数量,将没有其他可切换的线程从而导致必然有部分core是空闲的状态。具体来说,当模型预测时间较短时(<10ms),Server端线程数不宜过多(线程数=1——10倍core数量),否则线程切换带来的开销不可忽视。当模型预测时间较长时,Server端线程数应稍大一些(线程数=4——200倍core数量)。 + +# 3.异步模式 +当**大部分用户的Request请求batch数<<模型最大支持的Batch数**时,采用异步模式的收益是明显的。 + +异步模型的原理是将模型预测阶段与RPC线程脱离,模型单独开辟一个线程数可指定的线程池,RPC收到Request后将请求数据放入模型的线程池中的Task队列中,线程池中的线程从Task中取出数据合并Batch后进行预测,从而提升QPS,更多详细的介绍见[C++Serving功能简介](./Introduction_CN.md),同步模式与异步模式的数据对比见[C++ Serving vs TensorFlow Serving 性能对比](./Benchmark_CN.md),在上述测试的条件下,异步模型比同步模式快百分50%。 + + +异步模式的开启有以下两种方式。 +## 3.1 Python命令辅助启动C++Server + +`python3 -m paddle_serving_server.serve`通过添加`--runtime_thread_num 2`指定该模型开启异步模式,其中2表示的是该模型异步线程池中的线程数为2,该数值默认值为0,此时表示不使用异步模式。`--runtime_thread_num`的具体数值设置根据模型、数据和显卡的可用显存来设置。 + +通过添加`--batch_infer_size 32`来设置模型最大允许Batch == 32 的输入,此参数只有在异步模型开启的状态下,才有效。 + +## 3.2 命令行+配置文件启动C++Server + +此时通过修改`model_toolkit.prototxt`中的`runtime_thread_num`字段和`batch_infer_size`字段同样能达到上述效果。 + +# 4.多模型组合 +当**您的业务中需要调用多个模型进行预测**时,如果您追求极致的性能,您可以考虑使用C++Serving[自定义OP](./OP_CN.md)和[自定义DAG图](./DAG_CN.md)的方式来实现上述需求。 + +## 4.1 优点 +由于在一个服务中做模型的组合,节省了网络IO的时间和序列化反序列化的时间,尤其当数据量比较大时,收益十分明显(实测单次传输40MB数据时,RPC耗时为160-170ms)。 + +## 4.2 缺点 +1) 需要使用C++去自定义OP和自定义DAG图去定义模型之间的组合关系。 +2) 若多个模型之间需要前后处理,您也需要使用C++在OP之间去编写这部分代码。 +3) 需要重新编译Server端代码。 + +## 4.3 示例 +请参考[examples/C++/PaddleOCR/ocr/README_CN.md](../../examples/C++/PaddleOCR/ocr/README_CN.md)中`C++ OCR Service服务章节`和[Paddle Serving中的集成预测](./Model_Ensemble_CN.md)中的例子。 + +# 5.请求缓存 +当**您的业务中有较多重复请求**时,您可以考虑使用C++Serving[Request Cache](./Request_Cache_CN.md)来提升服务性能 + +## 5.1 优点 +服务可以缓存请求结果,将请求数据与结果以键值对的形式保存。当有重复请求到来时,可以根据请求数据直接从缓存中获取结果并返回,而不需要进行模型预测等处理(耗时与请求数据大小有关,在毫秒量级)。 + +## 5.2 缺点 + +1) 需要额外的系统内存用于缓存请求结果,具体缓存大小可以通过启动参数进行配置。 +2) 对于未命中请求,会增加额外的时间用于根据请求数据检索缓存(耗时增加1%左右)。 + +## 5.3 示例 +请参考[Request Cache](./Request_Cache_CN.md)中的使用方法 \ No newline at end of file -- GitLab