性能分析 - Ligcox/BTP_DM GitHub Wiki

概述

BTPDM是一个基于Python开发的框架,Python像Java/C#一样,也是一种根据虚拟机的语言。Python程序运行时,编译的结果被保存在内存中的PyCodeObject中,当Python程序运行结束时,Python解释器将PyCodeObject写回pyc文件中。Python作为一门脚本语言相对于编译型语言有以下显著的优势: 无需编译:在目标平台只有要解释器即可运行 开发效率高:Python是一门语法极为简洁的语言,能够显著缩短程序的开发时间。 生态丰富:在Pypi上能够找到全球开发者针对不同场景开发的库

但Python与C/C++相比也有显著的缺点:执行效率低下。因此,我们在这一章对BTPDM的运行进行一定的分析。

C++与Python的混合编程

在/src/Sample/c2py/展示了使用pybind11进行Python和C++混合编程的例子

既然解释型语言拥有较高的执行速度,为什么不使用C/C++进行较为简单的模块处理,然后在再由Python进行调用,这样是否能够提升BTPDM的整体运行效率。

事实上,大量的Python第三方模块均采用了C/C++和Python进行混合编程,例如NumPy、OpenCV等。通过Python接口调用这些模块时,底层仍使用的C++进行运算的。

我们在test_funcs.py展示了一个利用Python调用C++模块进行简单的例子。通过获取CPU级别精度的时间并对运行时间进行记录。结果发现,仅使用C++进行简单的数值运算(在样例中展示的是计算的两个数字之和)并返回Python使用与直接使用Python编程相比,纯Python的运行效率是C++混合编程的70倍!在C++中执行进行重复数值运算直接返回Python结果,C++展示出其性能的优越,和纯Python编程相比,C++混合编程的程序效率会高出10倍。

在调用函数的过程中,混合编程的大量时间不可避免地用于堆栈指针进行值传递,简单的运算纯Python编程却能够直接规避这一问题。而第三方库已经提供了各个平台下编译完成的.pyd文件(例如NumPy和OpenCV),调用这些库的过实际上已经不是使用Python实现进而提高了程序的运行效率。

从开发效率上来看,在混合编程的过程中,需要额外地编写类似setup.pywrap.cpp的wrap文件,这严重拖慢整个BTPDM的开发周期。另外,BTPDM主要为了快速适应RoboMaster赛场环境上的跨平台框架,使用混合编程的却依旧需要对混合编程部分进行编译,这无疑与BTPDM的开发初衷有所冲突。

综合来看,在较为复杂的运算使用C/C++进行混合编程能够对程序整体的运行效率有较大地提升,第三方库在这一点上已经做的较为完善。综合考虑开发周期、跨平台移植等一系列原因,在BTPDM的主体开发依然是使用Python进行的。此外,随着Python解释器具体实现的发展,类似PyPy解释器已经逐渐成熟,使得Python的运行效率能够接近C/C++。我们有理由相信(事实上我们接下来也会看到)使用Python作为主体开发语言的TPDM能够较好的适应RoboMaster赛场。

性能测试

在/src/Sample/performanceAnalysis/展示了使用BTPDM进行性能分析的例子
在/src/Sample/performanceAnalysis/也保存了性能分析的结果源文件,可以使用snakeviz等工具进行进一步的分析

测试环境

Python提供了Profilers 分析器cProfile和profile。profile 是一组统计数据,描述程序的各个部分执行的频率和时间。这些统计数据可以通过pstats模块格式化为报表。对于BTPDM的性能分析,我们使用了cProfile;这是一个C扩展插件,因为其合理的运行开销,所以适合于分析长时间运行的程序。该插件基于lsprof,由Brett Rosen和Ted Chaotter编写。

本章节中的性能测试将BTPDM在debug和非debug模式下分别运行一段时间(近似于main.py的运行时间)。程序运行环境为:

  • 开发板NVIDIA Jetson AGX
  • 系统版本Ubuntu 18.04.5 LTS
  • 功率模式MAXN

函数调用频率和时间

下面的表格分别展示了debug模式下和关闭debug模式后BTPDM运行一段时间,按照调用时间降序排列的前20条数据和Icicle风格的程序分析图。

debug模式

ncalls tottime percall cumtime percall function
164/1 0.012 0.000 384.682 384.682 {built-in method builtins.exec}
1 0.000 0.000 384.682 384.682 main.py:10()
1 0.131 0.131 383.566 383.566 scheduler.py:234(run)
24448 0.303 0.000 383.436 0.016 BCPloger.py:75(wrapper)
24448 0.921 0.000 383.092 0.016 scheduler.py:238(main_task)
24448 1.439 0.000 322.879 0.013 scheduler.py:186(task_auto_aiming)
24448 0.717 0.000 242.548 0.010 targetDetector.py:221(process)
48895 166.692 0.003 166.692 0.003 {waitKey}
24448 0.690 0.000 149.993 0.006 targetDetector.py:227(updateProcess)
24448 5.585 0.000 69.286 0.003 imageFilter.py:20(process)
24448 1.778 0.000 62.685 0.003 targetDetector.py:83(detectArmor)
24438 2.768 0.000 53.912 0.002 targetDetector.py:133(pnp_info)
24003 32.643 0.001 32.643 0.001 {solvePnP}
24448 2.464 0.000 29.113 0.001 targetDetector.py:35(detectLightStrip)
171136 23.855 0.000 23.855 0.000 {threshold}
24447 1.922 0.000 22.620 0.001 BCPloger.py:118(dispaly_loger)
24448 0.860 0.000 18.271 0.001 imageFilter.py:64(updateProcess)
1470633 17.553 0.000 17.553 0.000 {method 'write' of '_io.TextIOWrapper' objects}
73344 0.406 0.000 13.344 0.000 utils.py:160(debug_show_window)
48896 13.011 0.000 13.011 0.000 {cvtColor}
73344 12.937 0.000 12.937 0.000 {imshow}
48896 12.035 0.000 12.035 0.000 {blur}

debug Icicle图

关闭debug模式

ncalls tottime percall cumtime percall function
164/1 0.010 0.000 480.604 480.604 {built-in method builtins.exec}
1 0.000 0.000 480.604 480.604 main.py:10()
1 0.182 0.182 479.652 479.652 scheduler.py:234(run)
45211 0.449 0.000 479.470 0.011 BCPloger.py:75(wrapper)
45211 13.544 0.000 478.964 0.011 scheduler.py:238(main_task)
33882 1.425 0.000 389.672 0.012 scheduler.py:186(task_auto_aiming)
33882 7.307 0.000 197.204 0.006 imageFilter.py:20(process)
33882 0.733 0.000 177.910 0.005 targetDetector.py:221(process)
33882 1.527 0.000 79.050 0.002 imageFilter.py:64(updateProcess)
67762 78.735 0.001 78.735 0.001 {waitKey}
33881 2.490 0.000 77.507 0.002 targetDetector.py:83(detectArmor)
33880 3.792 0.000 65.016 0.002 targetDetector.py:133(pnp_info)
237173 58.930 0.000 58.930 0.000 {threshold}
33881 0.862 0.000 56.216 0.002 targetDetector.py:227(updateProcess)
203052 52.012 0.000 52.012 0.000 {built-in method numpy.core.multiarray.concatenate}
33882 3.538 0.000 43.430 0.001 targetDetector.py:35(detectLightStrip)
237174 36.958 0.000 36.958 0.000 {bitwise_and}
33763 36.318 0.001 36.318 0.001 {solvePnP}
33881 2.660 0.000 35.134 0.001 BCPloger.py:118(dispaly_loger)
2062742 27.884 0.000 27.884 0.000 {method 'write' of '_io.TextIOWrapper' objects}

非debug Icicle图

结果分析

具体使用过程中,可以在/src/main.py中切换DEBUG_MODEL的值开启和关闭debug模式
如:gvm.set_value("DEBUG_MODEL", "DEBUG_SETTING", False)表示关闭debug模式

BTPDM在测试环境下能够有效地对装甲板目标进行识别和侦测。在非debug模式下运行图像主处理任务平均调用时间为0.0115s,日志等非核心操作对程序性能影响为7.31%。性能主要花费在图像二值化、pnp位姿解算等方面。

debug模式

debug模式饼图

关闭debug模式

非debug Icicle图

可以看到,在debug模式下,一部分时间被花费在了cv2.imshow函数等debug刷新上,这部分函数对性能的影响超过17%!关闭debug后程序整体运行效率提升10.9567%,主要功能函数运行效率提升了12.9169%。BTPDM的BCP部分对1.0723%。

⚠️ **GitHub.com Fallback** ⚠️