寫(xiě)在前面
公司一些方案巍杈,在Andoird P上架構(gòu)必須要修改成HIDL颤芬,不然會(huì)遇到一系列的Selinux的問(wèn)題,所以決定還是按照標(biāo)準(zhǔn)的Android HIDL的架構(gòu)重新寫(xiě)了方案(因?yàn)楸容^機(jī)密煎饼,所以不透露具體方案代碼)位岔。但是我們的這個(gè)模塊對(duì)性能的要求非常高,不然咱們的設(shè)備怎么能打敗競(jìng)爭(zhēng)對(duì)手呢牺丙,怎么屹立在世界500強(qiáng)呢则涯,對(duì)吧。_
因?yàn)槲覀冏龅墓I(yè)設(shè)備冲簿,對(duì)實(shí)時(shí)性要求比較高粟判,但是HIDL的設(shè)計(jì)畢竟是需要進(jìn)程間通信來(lái)調(diào)用到比較low level的接口的,肯定會(huì)有一些性能損失峦剔,但是好在還有一些別的機(jī)制來(lái)挽回?fù)p失档礁,本文就來(lái)探討一下這個(gè)問(wèn)題,以及對(duì)不同的方式做一下性能的比對(duì)吝沫。
幾種不同的方式對(duì)比
我們的需求是呻澜,應(yīng)用層需要調(diào)用一些接口到kernel中的驅(qū)動(dòng),有一個(gè)HAL層封裝了對(duì)驅(qū)動(dòng)的操作野舶,應(yīng)用層去調(diào)用HAL層接口易迹,其實(shí)就跟標(biāo)準(zhǔn)的AOSP的模塊類(lèi)似。那么問(wèn)題就來(lái)了平道,以前是直接調(diào)用HAL接口睹欲,然后通過(guò)open/read/write/ioctl來(lái)跟驅(qū)動(dòng)通信就好了,比較關(guān)心性能損耗就是系統(tǒng)調(diào)用到kernel中的時(shí)間損耗一屋,其他都還好窘疮。但是一旦改成HIDL接口的寫(xiě)法,應(yīng)用層就變成了HIDL的client端冀墨,調(diào)用到HAL的server端是用過(guò)binder/hwbinder進(jìn)程間通信完成的闸衫,會(huì)有一些性能的損耗,我們就來(lái)測(cè)一下這個(gè)損耗是多少诽嘉,因?yàn)槲覀兊膽?yīng)用場(chǎng)景對(duì)這個(gè)時(shí)間比較關(guān)心蔚出,所以需要做這些分析弟翘,而且應(yīng)用層會(huì)頻繁的調(diào)用HAL接口。
如果比較難理解的話骄酗,我們來(lái)舉個(gè)例子:
這個(gè)例子是在Android Camera開(kāi)發(fā)過(guò)程中可能遇到的稀余,在camera預(yù)覽過(guò)程中,上層app需要實(shí)時(shí)的獲取camera采集的圖像數(shù)據(jù)趋翻,這個(gè)數(shù)據(jù)量是很大的睛琳,而且這個(gè)過(guò)程也是很復(fù)雜的,因?yàn)樵陬A(yù)覽過(guò)程中還需要調(diào)整ISP進(jìn)行不同效果參數(shù)的調(diào)整踏烙,在這個(gè)過(guò)程中對(duì)camera模塊進(jìn)行控制很頻繁(通過(guò)I2C把數(shù)據(jù)寫(xiě)到camera模組中)师骗。我們假設(shè)要寫(xiě)進(jìn)去的數(shù)據(jù)在應(yīng)用層,需要通過(guò)HAL層接口調(diào)用到驅(qū)動(dòng)中進(jìn)行真正的I2C通信讨惩。
左邊黃色的辟癌,是使用以前的HAL層架構(gòu),直接app進(jìn)程直接function call調(diào)用hall層的函數(shù)步脓,通過(guò)ioctl的system call把數(shù)據(jù)傳送到kernel層愿待。
右邊藍(lán)色的,是使用HIDL來(lái)實(shí)現(xiàn)app調(diào)用底層I2C操作靴患,分為兩部分仍侥,app作為HIDL的client端通過(guò)binder進(jìn)程間通信來(lái)調(diào)用server端的接口。
對(duì)我們而言鸳君,比較關(guān)心的就是proxy client端進(jìn)程間調(diào)用到server端的時(shí)間延遲农渊,畢竟是進(jìn)程間通信,肯定沒(méi)有直接調(diào)用來(lái)的快或颊。而且兩個(gè)不同進(jìn)程砸紊,就涉及到數(shù)據(jù)的拷貝,當(dāng)i2c需要寫(xiě)入的數(shù)據(jù)很大而且調(diào)用次數(shù)很多的時(shí)候這個(gè)拷貝和傳輸?shù)难舆t就會(huì)顯得比較突出了囱挑。
我們這里就使用幾種不同的調(diào)用方式來(lái)看時(shí)間延遲和效率問(wèn)題:
- 直接調(diào)用
- HIDL接口傳輸
- Oneway HIDL interface
參考代碼:
https://github.com/JayZhang0708/HIDL-4
最終調(diào)用干活的方法
#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;
}
很簡(jiǎn)單醉顽,其實(shí)就是遍歷傳過(guò)來(lái)的數(shù)組。
直接調(diào)用
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);
}
結(jié)果
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
代碼詳細(xì)就不介紹了平挑,之前的幾篇文章都有些游添。
結(jié)果顯而易見(jiàn),直接調(diào)用是很快的通熄,基本沒(méi)有啥lentency唆涝,使用HIDL接口傳輸數(shù)據(jù)會(huì)比較費(fèi)時(shí)間,我們這里傳輸了1000個(gè)byte所以比較明顯唇辨。
然后就是第三個(gè)廊酣,使用了oneway
package vendor.sample.benchmark@1.0;
interface IBenchmark {
writeMessage(SampleMessage message);
oneway writeMessageOneway(SampleMessage message);
};
一般來(lái)說(shuō)binder進(jìn)程間通信時(shí)同步的,也就是當(dāng)client調(diào)用接口到server的時(shí)候赏枚,需要server處理結(jié)束亡驰,然后返回給client晓猛。
當(dāng)我們把接口設(shè)置成oneway,就可以把接口的調(diào)用變成異步的凡辱,當(dāng)client調(diào)用接口的時(shí)候鞍帝,系統(tǒng)會(huì)重新起一個(gè)線程來(lái)處理client的函數(shù),調(diào)用接口的線程會(huì)直接返回煞茫。
所以當(dāng)我們有一些接口不需要得到返回值而且不需要block當(dāng)前線程的時(shí)候需要把接口定義為oneway來(lái)提升調(diào)用的效率,當(dāng)然了摄凡,如果你壓根就不考慮性能的話续徽,那就無(wú)所謂啦。
談?wù)剝?nèi)存共享
其實(shí)在用到比較大數(shù)據(jù)傳輸?shù)臅r(shí)候亲澡,最好的選擇是使用內(nèi)存共享來(lái)實(shí)現(xiàn)不同進(jìn)程間的通信钦扭,因?yàn)槭褂脙?nèi)存共享涉及到的代碼比較多,這里就不具體講了床绪,詳細(xì)的話后面有機(jī)會(huì)再單獨(dú)寫(xiě)一篇客情,在Android中如何使用內(nèi)存共享實(shí)現(xiàn)不同進(jìn)程間通信。
比較常見(jiàn)的例子就是Camera的HIDL實(shí)現(xiàn)了癞己,把framebuffer在kernel和應(yīng)用層不同進(jìn)程間進(jìn)行共享膀斋,實(shí)現(xiàn)buffer填充的時(shí)候零拷貝動(dòng)作。最常用的就是使用Android的ION框架來(lái)實(shí)現(xiàn)痹雅。
最后是FMQ
FMQ的出現(xiàn)就是為了解決HIDL接口通信性能差的仰担,我們后面單獨(dú)來(lái)講解Android的FMQ,其實(shí)在瀏覽AOSP原生的代碼中使用FMQ的場(chǎng)景不多绩社,在我自己的使用過(guò)程中摔蓝,我一般用作事件的通知,但是原來(lái)的寫(xiě)法就是使用HIDL 的callback機(jī)制來(lái)實(shí)現(xiàn)愉耙,我嘗試改為FMQ來(lái)實(shí)現(xiàn)贮尉,實(shí)測(cè)下來(lái)效果不是很明顯,但是會(huì)減小系統(tǒng)開(kāi)銷(xiāo)朴沿,不需要頻繁的進(jìn)程間通信了猜谚。
具體可以參考下面鏈接:
https://source.android.com/devices/architecture/hidl/fmq