算法从串行,到并行,再到流程和算法耦合并行.
最近蛮长的时间里, 都在配合开发一个多站点、多台扫描仪的点云采集系统。本次的优化主要在前期的数据采集阶段。
最初的实现逻辑是,每一个站点的N台扫描仪的点云合并,最终N 个站点的点云再进行合并。
效果验证后,实际使用时,无法满足甲方的要求,5个站点的情况下,整体流程(采集+计算分析)超过了10分钟,会导致整个流水线效率变低。
分析代码很快就发现了问题————既有合并逻辑,是完全采取了串行执行,即,依次将每个站点中扫描仪的点云进行合并,再N个站点的点云进行合并。这导致用时很长,并且CPU 没有拉满。
第一版方案,很快就诞生了————基于进程池,把每个站点看作一个任务单元,并行执行每个站点的合并。然后再执行N 个站点点云的合并。同时结合AI ,对代码中带来性能损耗的地方做了修复,合并算法从3m40s 缩短到了40s————效果提升很明显,CPU 拉满的效果也很明显。
但随着走出实验室,应用到工厂,新的问题出现了————设定的既有5个站点点位,不够,需要增加到11 个站点点位。
数据体量一下子翻倍————但没有办法成倍调整进程池的进程数,因为每个进程内部都调用了open3d 做一些计算,主要是点云配准。open3d 是python 的C 绑定,它本身就能利用多核。当第一版配置多进程时,其实已经在部分计算时,出现了算力的争夺,整体时间没有出现成倍减少。
于是, 再次踏上分析代码的征程。
首先是,检查了从采集到合并完结,整体的时间,以及耗时最久的几个调用————排除了 PLC 的移动,因为这块不在我们的可控范围内,如果移速配置过快,会出现异常停止,应该是机械结构设计有问题导致的,这是两端双电机实现。
- 采集
单个站点采集用时25s,11 个站点采集用时就多了。
- 合并
合并从原有的40秒增加到了一分钟以上————因为进程池配置的进程数量限制,需要多次执行了。
这里,计划采取的优化方案是,两个角度:
- 将计算放到采集期间
此前的逻辑是,采集完毕后,再统一进行合并。但是计算并不是需要采集完才能开始,而是每采集完一个数据,就能计算一小步。
那逻辑上,当采集完最后一个点云数据时,前面的11 组点云的合并任务,已经可以开始计算, 并且如果PLC 位移用时大于计算用时, 那么前面的合并任务都应该处于完结状态(实际PLC 位移时间大于单站点点云处理时间).
那么, 11 个站点合并后的点云,10 个站点都已经完成了第二轮处理,不管多少个点位,都只会剩下最后一个站点的数据需要额外耗时处理,以及站点间合并时,只保留了最后一个点云和倒数第二个点云的精配准.
然后最后全部合并起来。
优化采集
- 采集链接
当前实现的做法,是每次采集时,都通过cti 重新建立链接,重新配置参数,在执行扫描,最后转换成open3d 的点云并保存到磁盘。
但是链接和参数配置,可以在程序启动就进行初始化,这里理论上能从25s 缩短到15s-16s.
- 不在采集期间转换格式
采集返回的buffer,到numpy,到point3d 对象,耗时3s-4s,并且主要体现在numpy 格式转open3d,几乎占满3s,那可以当采集完毕后直接返回矩阵,新开进程异步处理成open3d点云并保存,使得采集这个环节不阻塞。这里每台扫描仪还能再缩短3-4s。
汇总一下:在单站点的采集(两台扫描仪),预想的是,能够从25s,缩短到6s左右。
实际展开时,主要还是依赖于asyncio 的future 占位,来实现很乱的异步执行,和统一的等待接收,相比之下进程线程原型,这里完成起来会很复杂。
最终的效果是,在实验室,只有两个站点时,单个站点的采集流程,缩短到了7s-8s(这里的数字应该可以推广应用到11个点位时的耗时),当采集完成,到合并结束,中间用时10s(这里暂时无法精确推算11个点位时计算耗时,但从算法的设计上,应该只固定保留了最后一个站点的合并,以及固定倒数第二个站点点云和最后一个站点点云的精配准计算,以及动态的N个站点点云的直接相加,其中动态增长部分的计算耗时很少,主要的耗时都在前面的精配准中),预估11个站点用时应该也在10s出头。
那么目前第二轮的成果是:
- 25s -> 7s-8s
- 60+s -> 10s
整体用时优化下来将近4分钟了