上一篇教程我们讲述了怎么使用Python连接CoppeliaSim对里面的无人机进行控制,也讲述了怎么通过多进程的方式进行调用。在大多数情况下学会这些手段就足够进行仿真操作,可将仿真的重点转到算法的验证上,然而对于群体无人机算法的验证则是个例外。
这类算法的验证往往需要对一堆无人机进行仿真,如果不对仿真性能进行优化,跑一次实验可能得半天到一天。这样一来速度慢不说,很多bug的调试也会因为无法快速获得反馈而变得无从下手。
在本篇教程中,我们将深入探讨如何对仿真软件进行性能优化,以实现快速的大规模无人机集群仿真。
测试环境搭建
首先,我们在仿真平台上放入20台无人机,如下图所示:
然后,用单线程阻塞的方式控制这20台无人机朝一个方向移动5个时间单位,代码如下:
import sim
import time
sim.simxFinish(-1) # 断开一切可能的连接
# 开启一个与服务器(V-REP)的一个通信线程。返回一个ClientID,这个ClientID后续的API基本都需要用到,可以理解为用这个ID来进行通信。
clientID = sim.simxStart('127.0.0.1', 19997, True, True, 5000, 5) # 连接到CoppeliaSim场景中
if clientID != -1:
print('Connected to remote API server')
time_start = time.time() # 记录开始时间
# 用名字来检索一个对象句柄。仿真环境里任何添加的object都会有一个自己的句柄(一个int,比如85,102),然后这个句柄跟object的名字对应,也就是我们有了名字就能对应到句柄。
for run in range(5):
for i in range(20):
objName = "./Quadcopter[%s]" % i
print(objName)
_, targetObj = sim.simxGetObjectHandle(clientID, objName, sim.simx_opmode_oneshot_wait)
# 获取无人机对象信息
_, arr = sim.simxGetObjectPosition(clientID, targetObj, -1, sim.simx_opmode_oneshot_wait)
print(arr)
arr[1] = arr[1] + 0.5
sim.simxSetObjectPosition(clientID, targetObj, -1, (arr[0], arr[1], arr[2]), sim.simx_opmode_oneshot_wait)
time_end = time.time() # 记录结束时间
time_sum = time_end - time_start # 计算的时间差为程序的执行时间,单位为秒/s
print(time_sum)
else:
print('Failed connecting to remote API server')
sim.simxGetPingTime(clientID) # 在断开之前连接确保之前的命令已经被执行完成
sim.simxFinish(clientID) # 断开与CoppeliaSim场景的连接
可以看到,在一台 i9-11900K的台式机上跑完一次代码需要49秒多。
优化方法一:设置模拟循环时间步长
有了测试场景之后,我们来看看最简单的加速方式(不要改任何代码),那就是设置模拟循环的时间步长。
软件的默认步长为50ms,将其改为10ms然后再运行程序,可以发现跑完一次代码的时间缩短到15秒多。
优化方法二:修改代码,减少阻塞
对代码进行分析,可以非常容易定位到代码运行速度慢的原因在于阻塞语句上。因此为了加速,可以尽可能减少阻塞代码的调用。比如获取对象句柄的语句只在开始调用,而位置设置语句写成非阻塞的形式。修改后的代码如下:
import sim
import time
sim.simxFinish(-1) # 断开一切可能的连接
# 开启一个与服务器(V-REP)的一个通信线程。返回一个ClientID,这个ClientID后续的API基本都需要用到,可以理解为用这个ID来进行通信。
clientID = sim.simxStart('127.0.0.1', 19997, True, True, 5000, 5) # 连接到CoppeliaSim场景中
targetObj={}# 创建空列表
if clientID != -1:
print('Connected to remote API server')
time_start = time.time() # 记录开始时间
for i in range(18):
objName = "./Quadcopter[%s]" % i
print(objName)
_, targetObj[i] = sim.simxGetObjectHandle(clientID, objName, sim.simx_opmode_oneshot_wait)
for run in range(5):
for i in range(18):
# 获取无人机对象信息
_, arr = sim.simxGetObjectPosition(clientID, targetObj[i], -1, sim.simx_opmode_oneshot_wait)
print(arr)
arr[1] = arr[1] + 0.5
sim.simxSetObjectPosition(clientID, targetObj[i], -1, (arr[0], arr[1], arr[2]), sim.simx_opmode_oneshot)
time_end = time.time() # 记录结束时间
time_sum = time_end - time_start # 计算的时间差为程序的执行时间,单位为秒/s
print(time_sum)
else:
print('Failed connecting to remote API server')
sim.simxGetPingTime(clientID) # 在断开之前连接确保之前的命令已经被执行完成
sim.simxFinish(clientID) # 断开与CoppeliaSim场景的连接
为了体现修改的效果,我们先将步长设置回50ms,然后运行代码。可以发现跑完一次代码需要的时间缩短到17秒多。如果步长为10ms,则缩短到5秒多。这相比于原来的仿真速度有了接近10倍的提升。
优化方法三:直接用仿真器进行仿真
如果上述的方法速度提升还是不够,那还有一个终极的解决方法,就是完全回避阻塞的问题,用仿真器直接进行仿真。
首先需要先把默认的脚本删掉,流程如下:无人机对象上方右键→Edit→Remove→Associated child script
然后添加python脚本,流程如下:无人机对象上方右键→Add→Associated child script→Threaded→Python
到CoppeliaSim的安装路径(C:\Program Files\CoppeliaRobotics\CoppeliaSimEdu\system)下,找到usrset.txt文件并打开,找到defaultPython,设置为本机Python的路径。找到executeUnsafe,设置为true:
defaultPython = C:\Users\38391\AppData\Local\Programs\Python\Python37\python.exe
executeUnsafe=true
然后,安装需要的Python模块
pip install pyzmq
pip install cbor
重启CoppeliaSim使设置生效,然后双击脚本写入如下代码(注意每台无人机都需要做一遍,可以先设置完一台无人机之后进行复制。复制的话代码中的objNum要做相应修改):
#python
import time
objNum=0
def sysCall_thread():
sim.setThreadAutomaticSwitch(True)#Setting Thread Automatic Switch
handle=sim.getObject('../Quadcopter[%s]' % objNum)
time_start = time.time()
runTime=0
while True:
p=sim.getObjectPosition(handle,-1)
#print(p)
p[0]=p[0]+0.5
sim.setObjectPosition(handle,-1,p)
runTime=runTime+1
if runTime==5:
time_end = time.time()
time_sum = time_end - time_start
print(time_sum)
pass
完成上述操作后,跑一下代码可以发现运行时间在最坏的情况下也只需要不到0.5秒,相比于一开始的仿真速度有了接近100倍的提升。