1. via topic

上圖是以前ROS課上做的一個實驗,內容是測試一個publisher和一個subscriber之間通訊所用的時間。兩個node都很簡單,publisher發送一個字符串,字符串帶有標號;subscriber回顯該字符串,字符串長度不超過20個char。
怎么去標定發送時間是接收時間呢?目前使用的方法就是在publisher發送前使用ROS_INFO輸出一個消息,消息會帶有ROS的時間戳;subscriber的callback函數里邊也使用ROS_INFO輸出一個帶時間戳的消息。雖然說這種方法並不是非常精准,但目前也沒有想到更好的辦法了,哪怕是使用header里邊的time stamp也有一個獲得當前時間和賦值的過程,難以百分百精確,所以目前只能這樣粗略測量。
根據實驗數據可以發現在同一台機器上,兩個node通過topic通訊,大致產生0.7ms的延遲。(本人機子i5-3210M/4G)
2. via service

這張圖是今天自己做的測試,可以看到平均時間大約在3ms左右。
測量的方法也是跟上面的類似。Client在call之前先輸出一個帶時間戳的消息,server在接收到請求執行操作前也會輸出一個帶時間戳的消息。這次client與server之間傳遞的消息更短,是兩個int。
從上圖我們不難發現,通過service通訊居然比topic延遲要高?實在是不願意相信,因為這跟我一開始的理解是相違背的。為了控制實驗環境平台的一致性(我從groovy換到了indigo),於是重做了以前的那個實驗,粗測通過topic傳輸的延遲時間,發現依然是0.7ms左右。
理論速度
為何前面的實驗結果讓我如此驚訝?
官方文檔中寫明,client與server之間維持着一個持久連接。對於這種RPC請求/回復機制,官方給出的評價是“面對低魯棒性的服務程序變更有着更好的性能表現”(higher performance at the cost of less robustness to service provider changes)1。
說到持久連接,不禁讓人想起ROSTCP。照這么說,如果在ROS架構下node之間通訊的底層實現都是通過ROSTCP/ROSUDP的話,那理應via service應該跟via topic的速度相當,如果將“持久性”納入考慮范圍的話,service甚至應該比topic更快一些才對。但實際情況是via service的時延是via topic的4倍左右。如此大的差距,實在讓人百思不得其解。
實現機制——同步與異步
其實在開始寫這篇博文的時候,我都依然沒有想明白這個問題。邊寫邊查資料,看到ros answer上一位大大在描述這兩種機制時,用到了asynchronous和synchronous這兩個字眼2。回到宿舍一邊洗着冷水澡一邊在想,忽然靈光一現!是的,實驗的結果是沒有問題的,有問題的是我的實驗方法!
- Asynchronous Topic
Topic是異步的。簡簡單單的一句話,里邊卻隱含了千言萬語。讓我關注起這一點的原因除了ros answer上面的那篇問答,還有一個就是自己做的另一個實驗:創建一個publisher,pub的速度是100Hz,發送緩沖區的大小只有1;創建一個subscriber,調用callback函數的的速度是1Hz(用spinOnce),但接收緩沖區的大小是1000。這樣的效果就是,publisher近乎勻速地發送着數據,每秒100次;而subscriber每一秒調用一次callback函數(spinOnce),每次調用都一次性地處理接收緩沖區內的所有數據。由於接收緩沖區大小遠大於發送頻率,所以接收緩沖區不存在滿的情況,所以就能看到subscriber每秒都會很快地處理完100條消息,然后進入休眠,再被喚醒執行callback,再休眠……
而之前用於測試的subscriber,我使用的是spin。這也就意味着,回調函數一直是處於類似於忙等待的狀態的,一旦發現接收緩沖區內有數據,即刻取出作運算(前提當然是獲得CPU分片時間)。這也就說明了為什么實驗得出的結果via topic會更快!但要注意了,忙等待是會占用硬件資源的。在這種簡單的實驗條件下,via topic固然是很快,但當一個大項目跑起來時,它之前的高性能表現可能要大打折扣。
- Synchronous Service
至於server,在基於RPC請求/回復機制的前提下,它在接收到調用請求前都是處於休眠狀態的。Client的call請求將server喚醒,然后server執行請求,再返回結果給client。所以之前在計時的時候,大部分的時間都是消耗在將server從休眠狀態喚醒上了!所以才會看起來比via topic慢。
分析到這里,我就在想,那真正的client與server之間通訊的時延,是不是可以利用server返回結果給client這段時間算出呢?這才是真正拋開了將server從休眠喚醒的時間影響,是client與server通訊的真正時延!於是又有了下圖: 
果不其然!表中數據的中位數應該在0.3ms左右,也就是說,從server返回結果到client接收到結果,只需要0.3ms的時間!盡管使用spin通過topic轉發數據類似於忙等待(也許ROS的內部機制會對spin的頻率作最高限制),但也需要大約0.7ms的時間!而service機制中,client發出call請求之后就會進入阻塞狀態,直至server返回結果,期間的時延是via topic的一半而已!
而從這個數據我們還可以估計出將休眠狀態的server喚醒,所需要的時間大約是2.7ms。如果計算一來一回,client從發出call請求到接收返回結果,除去請求被執行的時間,在通訊上耗費的時間約是3.3ms,其中將server喚醒的時間約占82%!
- Conclusion
Topic與Service各有優劣。在設計項目的時候,要周全考慮各個方面的因素。Service比較適合用於執行復雜的、調度次數較低的任務。而topic則適合在通訊頻率高的情況下使用。再者,使用ros::Rate和sleep合理設置程序的執行速率,能使程序彈性更大,增強可維護性。使用topic時,也別忘了關注一下緩沖區的大小設置。
優劣分析
Service
- 優點:
- Client只需要關注發出命令請求、接收反饋,而無須關注底層實現,系統可維護性高
- Client維護着一個與server的持久連接(persistent connection),在單純的數據傳輸上可以做到更快,尤其是在分布式作業時
- 缺點:
- 當server單方改變執行模式或者存在bug的時候,client無法得知具體情況,因為server程序對client而言是透明的。在這種情況下,client只能得到一個錯誤的數據,或者被告知執行失敗,甚至是等待超時
- Server喚醒耗時太大,不建議用於通訊頻率高的情況
Topic
- 優點:
- 能非常方便地通過監聽topic的方法查看各個node的運行狀態等信息,可維護性高
- 能根據實際情況調整發送速率、發送/接收緩沖區大小,使之適應項目需求
- Topic是多對多的,可操作性強;而service只能一對多(一個server應對多個client)
- 缺點:
- 因為是異步架構,回調函數的執行頻率設計對系統整體性能影響很大
- 對於使用頻率不高的程序,若使用topic進行信息交換,想要減少額外系統開銷的辦法就是降低回調的速率。但一旦降低回調的速率,同時受到負面影響的還有系統的實時性
