寫在前面
公司一些方案,在Andoird P上架構必須要修改成HIDL,不然會遇到一系列的Selinux的問題,所以決定還是按照標準的Android HIDL的架構重新寫了方案(因為比較機密,所以不透露具體方案代碼)。但是我們的這個模塊對性能的要求非常高,不然咱們的設備怎么能打敗競爭對手呢,怎么屹立在世界500強呢,對吧。_
因為我們做的工業(yè)設備,對實時性要求比較高,但是HIDL的設計畢竟是需要進程間通信來調用到比較low level的接口的,肯定會有一些性能損失,但是好在還有一些別的機制來挽回損失,本文就來探討一下這個問題,以及對不同的方式做一下性能的比對。
幾種不同的方式對比
我們的需求是,應用層需要調用一些接口到kernel中的驅動,有一個HAL層封裝了對驅動的操作,應用層去調用HAL層接口,其實就跟標準的AOSP的模塊類似。那么問題就來了,以前是直接調用HAL接口,然后通過open/read/write/ioctl來跟驅動通信就好了,比較關心性能損耗就是系統(tǒng)調用到kernel中的時間損耗,其他都還好。但是一旦改成HIDL接口的寫法,應用層就變成了HIDL的client端,調用到HAL的server端是用過binder/hwbinder進程間通信完成的,會有一些性能的損耗,我們就來測一下這個損耗是多少,因為我們的應用場景對這個時間比較關心,所以需要做這些分析,而且應用層會頻繁的調用HAL接口。
如果比較難理解的話,我們來舉個例子:
這個例子是在Android Camera開發(fā)過程中可能遇到的,在camera預覽過程中,上層app需要實時的獲取camera采集的圖像數(shù)據(jù),這個數(shù)據(jù)量是很大的,而且這個過程也是很復雜的,因為在預覽過程中還需要調整ISP進行不同效果參數(shù)的調整,在這個過程中對camera模塊進行控制很頻繁(通過I2C把數(shù)據(jù)寫到camera模組中)。我們假設要寫進去的數(shù)據(jù)在應用層,需要通過HAL層接口調用到驅動中進行真正的I2C通信。
左邊黃色的,是使用以前的HAL層架構,直接app進程直接function call調用hall層的函數(shù),通過ioctl的system call把數(shù)據(jù)傳送到kernel層。
右邊藍色的,是使用HIDL來實現(xiàn)app調用底層I2C操作,分為兩部分,app作為HIDL的client端通過binder進程間通信來調用server端的接口。
對我們而言,比較關心的就是proxy client端進程間調用到server端的時間延遲,畢竟是進程間通信,肯定沒有直接調用來的快。而且兩個不同進程,就涉及到數(shù)據(jù)的拷貝,當i2c需要寫入的數(shù)據(jù)很大而且調用次數(shù)很多的時候這個拷貝和傳輸?shù)难舆t就會顯得比較突出了。
我們這里就使用幾種不同的調用方式來看時間延遲和效率問題:
- 直接調用
- HIDL接口傳輸
- Oneway HIDL interface
參考代碼:
https://github.com/JayZhang0708/HIDL-4
最終調用干活的方法
#define LOG_TAG "Sample#Lib"
#include <log/log.h>
#include <string.h>
#include "sample.h"
int writeMessage(uint8_t *data, int32_t size)
{
int i;
uint8_t tmp = 0;
for(i = 0; i < size; i++) {
tmp += data[i];
tmp &= 0xff;
}
return tmp;
}
很簡單,其實就是遍歷傳過來的數(shù)組。
直接調用
void test_function_call(void)
{
android::StopWatch stopWatch("test_function_call");
writeMessage(buffer, BUFFER_SIZE);
}
HIDL接口傳遞
void test_hidl_interface(void)
{
SampleMessage message;
message.size = BUFFER_SIZE;
message.data.resize(BUFFER_SIZE);
::memcpy(&message.data[0], buffer, BUFFER_SIZE);
android::StopWatch stopWatch("test_hidl_interface");
benchmark->writeMessage(message);
}
Oneyway HIDL接口
void test_oneway_hidl_interface(void)
{
SampleMessage message;
message.size = BUFFER_SIZE;
message.data.resize(BUFFER_SIZE);
::memcpy(&message.data[0], buffer, BUFFER_SIZE);
android::StopWatch stopWatch("test_oneway_hidl_interface");
benchmark->writeMessageOneway(message);
}
結果
04-30 04:14:09.425 29520 29520 D StopWatch: StopWatch test_function_call (us): 1
04-30 04:14:09.426 29520 29520 D StopWatch: StopWatch test_hidl_interface (us): 325
04-30 04:14:09.426 29520 29520 D StopWatch: StopWatch test_oneway_hidl_interface (us): 98
代碼詳細就不介紹了,之前的幾篇文章都有些。
結果顯而易見,直接調用是很快的,基本沒有啥lentency,使用HIDL接口傳輸數(shù)據(jù)會比較費時間,我們這里傳輸了1000個byte所以比較明顯。
然后就是第三個,使用了oneway
package vendor.sample.benchmark@1.0;
interface IBenchmark {
writeMessage(SampleMessage message);
oneway writeMessageOneway(SampleMessage message);
};
一般來說binder進程間通信時同步的,也就是當client調用接口到server的時候,需要server處理結束,然后返回給client。
當我們把接口設置成oneway,就可以把接口的調用變成異步的,當client調用接口的時候,系統(tǒng)會重新起一個線程來處理client的函數(shù),調用接口的線程會直接返回。
所以當我們有一些接口不需要得到返回值而且不需要block當前線程的時候需要把接口定義為oneway來提升調用的效率,當然了,如果你壓根就不考慮性能的話,那就無所謂啦。
談談內存共享
其實在用到比較大數(shù)據(jù)傳輸?shù)臅r候,最好的選擇是使用內存共享來實現(xiàn)不同進程間的通信,因為使用內存共享涉及到的代碼比較多,這里就不具體講了,詳細的話后面有機會再單獨寫一篇,在Android中如何使用內存共享實現(xiàn)不同進程間通信。
比較常見的例子就是Camera的HIDL實現(xiàn)了,把framebuffer在kernel和應用層不同進程間進行共享,實現(xiàn)buffer填充的時候零拷貝動作。最常用的就是使用Android的ION框架來實現(xiàn)。
最后是FMQ
FMQ的出現(xiàn)就是為了解決HIDL接口通信性能差的,我們后面單獨來講解Android的FMQ,其實在瀏覽AOSP原生的代碼中使用FMQ的場景不多,在我自己的使用過程中,我一般用作事件的通知,但是原來的寫法就是使用HIDL 的callback機制來實現(xiàn),我嘗試改為FMQ來實現(xiàn),實測下來效果不是很明顯,但是會減小系統(tǒng)開銷,不需要頻繁的進程間通信了。
具體可以參考下面鏈接:
https://source.android.com/devices/architecture/hidl/fmq