背景
在SDK開發(fā)迭代過程中需要嚴(yán)格關(guān)注SDK的性能問題,因此SDK通常會設(shè)計Monitor接口,對主要方法進(jìn)行性能打點監(jiān)測烹困,起先用的是System.currentTimeMillis(),然而實操后發(fā)現(xiàn),部分方法的耗時在1ms以內(nèi)蔫巩,大約幾十微秒的區(qū)間,這就hin尷尬了快压。
所以打算切換到System.nanoTime()圆仔,但是問題來了:
- System.nanoTime()拿的到底是什么時間,能不能準(zhǔn)確測量出方法耗時呢蔫劣?
- System.nanoTime()本身耗時是怎樣的呢坪郭?其本身會不會影響到方法的性能數(shù)據(jù)呢?
實驗設(shè)計
對于問題1脉幢,RTFSC總是沒錯的歪沃,以Linux下JDK1.8為例(1.7也是這份代碼)
jlong os::javaTimeNanos() {
if (Linux::supports_monotonic_clock()) {
struct timespec tp;
int status = Linux::clock_gettime(CLOCK_MONOTONIC, &tp);
assert(status == 0, "gettime error");
jlong result = jlong(tp.tv_sec) * (1000 * 1000 * 1000) + jlong(tp.tv_nsec);
return result;
} else {
timeval time;
int status = gettimeofday(&time, NULL);
assert(status != -1, "linux error");
jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec);
return 1000 * usecs;
}
}
其中,clock_gettime方法的第一個參數(shù)CLOCK_MONOTONIC決定了最終獲取的時間類型嫌松,CLOCK_MONOTONIC從字面意思來看是單調(diào)增加的時間沪曙,代碼的跟蹤結(jié)果也顯示其代表著距離系統(tǒng)啟動那一刻的時間差,本質(zhì)上是一個差值萎羔,由于代碼較多液走,這里不便貼出,有興趣的同學(xué)可以自行研究下。
既然獲取的是距離系統(tǒng)啟動時刻的時間差缘眶,那如果方法在執(zhí)行的過程中被其它方法中斷了呢腻窒?通過程序驗證,此時System.nanoTime()獲取的時間會包含其它方法執(zhí)行的時間磅崭。那么儿子,有沒有可能只獲得需要測試的方法執(zhí)行的時間呢?不管測試的方法有沒有被中斷砸喻,也就是說柔逼,除了CLOCK_MONOTONIC,有沒有其它參數(shù)能達(dá)成上述目標(biāo)呢割岛?
當(dāng)然是有的愉适,只需要傳入線程的clock id即可只獲取當(dāng)前需要測試的線程的執(zhí)行時間,從而排除其它線程的干擾癣漆,代碼如下(代碼是Android項目的jni代碼)
Java_myapplication_MainActivity_getThreadUserTime(
JNIEnv *env,
jobject /* this */) {
struct timespec ts;
clockid_t cid;
jlong ret = 0;
if (0 == pthread_getcpuclockid(pthread_self(), &cid)) {
if (0 == clock_gettime(cid, &ts)) {
ret = ts.tv_sec*NSEC_PER_SEC + ts.tv_nsec;
}
}
std::string hello = "Hello from C++";
return ret;
}
實驗進(jìn)行到這里维咸,已經(jīng)可以回答問題1了:System.nanoTime()拿到的是距離系統(tǒng)啟動時刻的時間差,在某些情況下并不能十分準(zhǔn)確的測量出方法耗時惠爽。
對于問題2癌蓖, 結(jié)合上述實驗,首先要比較下兩種方法即System.nanoTime()與JNI方法的各自耗時婚肆,測試手機(jī)為Google Pixel2租副,測試代碼如下
for (int j = 0; j < 10; j++) {
long sum = 0;
for (int i = 0; i < 10000; i++) {
long t1 = getThreadUserTime();
long t2 = getThreadUserTime();
sum += (t2 - t1);
}
long sum2 = 0;
for (int i = 0; i < 10000; i++) {
long t1 = System.nanoTime();
long t2 = System.nanoTime();
sum2 += (t2 - t1);
}
String info = (String) tv.getText();
tv.setText(info + "\nUserTime 10000次 TotolTime=" + sum + "\n" + "nanoTime 10000次 TotolTime=" + sum2);
測試結(jié)果如下(圖中時間單位為ns,納秒)
從結(jié)果可以看出较性,JNI方法的耗時是System.nanoTime()的三倍左右用僧,這也是可預(yù)期的,因為前者獲取線程的執(zhí)行時間是需要計算的赞咙,而后者相當(dāng)于只讀取一個數(shù)值即可责循,另一方面System.nanoTime()的執(zhí)行耗時大約是0.46us。
實驗進(jìn)行到這里攀操,已經(jīng)可以回答問題2了:
如果需要測試的方法耗時在幾十us以上院仿,
方法執(zhí)行的線程容易被中斷,那么建議使用JNI方法
方法執(zhí)行的線程幾乎不會被中斷(對于多核手機(jī)而言崔赌,這種情況將會占據(jù)大多數(shù))意蛀,兩種方式都o(jì)k
如果需要測試的方法耗時在幾u(yù)s左右,
同樣方法執(zhí)行的線程容易被中斷健芭,那么建議使用JNI方法
方法執(zhí)行的線程幾乎不會被中斷(對于多核手機(jī)而言县钥,這種情況將會占據(jù)大多數(shù)),那么建議使用System.nanoTime()
總結(jié)
當(dāng)下越來越多的性能分析需要更精確的耗時測量慈迈,不能無腦使用System.currentTimeMillis()了若贮。而在切換到System.nanoTime()后省有,也需要根據(jù)應(yīng)用場景考慮到計時方法本身的限制性了。