System.nanoTime()和System.currentTimeMillis()性能問題


​ 之前給模塊做性能優化的時候,需要將性能調到毫秒級,使用了System.nanoTime()和System.currentTimeMillis()對代碼分片計時分析耗時操作,后發現在串行情況下性能達到毫秒級,但是一旦在並發壓測的時候,性能急劇下降,后經多方排查,發現原因出在System.nanoTime()和System.currentTimeMillis()這兩個api上,其在並發情況下耗時會急劇上升,當然在整體上看依然很快,但是在高性能場景下就有很顯著的影響。特此記錄一下。

​ 測試代碼:

package cord;

import java.util.concurrent.CountDownLatch;

/**

  • Created by cord on 2018/5/7.
    */
    public class SystemApiPerfTest {

    public static void main(String[] args) throws InterruptedException {
    int count = 100;
    /**並發*/
    long interval = concurrentTest(count, ()->{System.nanoTime();});
    System.out.format("[%s] thread concurrent test <nanoTime> cost total time [%s]ns, average time [%s]ns.\n", count, interval, interval/count);

    <span class="hljs-comment">/**串行循環*/</span>
    interval = serialNanoTime(count);
    System.out.format(<span class="hljs-string">"[%s] count serial test &lt;nanoTime&gt; cost total time [%s]ns, average time [%s]ns.\n"</span>, count, interval, interval/count);
    
    System.out.println(<span class="hljs-string">"-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-"</span>);
    
     <span class="hljs-comment">/**並發*/</span>
     interval = concurrentTest(count, ()-&gt;{System.currentTimeMillis();});
     System.out.format(<span class="hljs-string">"[%s] thread concurrent test &lt;currentTimeMillis&gt; cost total time [%s]ns, average time [%s]ns.\n"</span>, count, interval, interval/count);
    
     <span class="hljs-comment">/**串行循環*/</span>
     interval = serialCurrentTime(count);
     System.out.format(<span class="hljs-string">"[%s] count serial test &lt;currentTimeMillis&gt; cost total time [%s]ns, average time [%s]ns.\n"</span>, count, interval, interval/count);
    

    }

    private static long concurrentTest(int threads, final Runnable r) throws InterruptedException {
    final CountDownLatch start = new CountDownLatch(1);
    final CountDownLatch end = new CountDownLatch(threads);

     <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; threads; i++) {
         <span class="hljs-keyword">new</span> Thread(() -&gt; {
             <span class="hljs-keyword">try</span> {
                 start.await();
                 <span class="hljs-keyword">try</span> {
                     r.run();
                 }<span class="hljs-keyword">finally</span> {
                     end.countDown();
                 }
             } <span class="hljs-keyword">catch</span> (InterruptedException e) {
                 e.printStackTrace();
             }
         }).start();
     }
    
     <span class="hljs-keyword">long</span> stime = System.nanoTime();
     start.countDown();
     end.await();
     <span class="hljs-keyword">return</span> System.nanoTime() - stime;
    

    }

    private static long serialNanoTime(int count){
    long stime = System.nanoTime();
    for (int i = 0; i < count; i++) {
    System.nanoTime();
    }
    return System.nanoTime() - stime;
    }

    private static long serialCurrentTime(int count){
    long stime = System.nanoTime();
    for (int i = 0; i < count; i++) {
    System.currentTimeMillis();
    }
    return System.nanoTime() - stime;
    }
    }

測試結果如下:

[100] thread concurrent test <nanoTime> cost total time [5085539]ns, average time [50855]ns.
[100] count serial test <nanoTime> cost total time [2871]ns, average time [28]ns.
-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
[100] thread concurrent test <currentTimeMillis> cost total time [7678769]ns, average time [76787]ns.
[100] count serial test <currentTimeMillis> cost total time [4103]ns, average time [41]ns.

串行情況下耗時趨於穩定,但是在並行情況下就不一樣了。

因為這兩個api都是native方法,涉及到系統層級的調用,與平台底層實現有關。

其實在串行情況下這兩個api其實性能很好,但是在並發情況下回急劇下降,原因在於計時器在所有進程之間共享,並且其還一直在發生變化,當大量線程嘗試同時去訪問計時器的時候,就涉及到資源的競爭,於是也就出現並行效率遠低於串行效率的現象了。所以在高並發場景下要慎重使用System.nanoTime()和System.currentTimeMillis()這兩個API。

附加資料:

linux上使用的計時器一般有兩種: TSC, HPET

HPET計時器(HPET Timer):高精度事件計時器,也是外部硬件計時器,固定頻率14.31818MHz。

TSC計時器(TSC Timer):時間戳計數計時器,是基於硬件的計時器,但頻率可變。以前它就等於處理器頻率,在早些年不是問題,但后來處理器不斷加入會降低頻率的擴展頻譜、電源管理等功能,就有問題了,於是后來設計的時候將其改為和處理器頻率相獨立。

HPET的性能相對TSC的性能要低

(注: 等級越高的時鍾越容易被系統使用)

等級 1 ~ 99 100 ~ 199 200 ~ 299 300 ~ 399 400 ~ 499
特點 非常差的時鍾源,只能作為最后的選擇。如 jiffies 基本可以使用但並非理想的時鍾源。如 PIT 正確可用的時鍾源。如 ACPI PM Timer,HPET 快速並且精確的時鍾源。如 TSC 理想時鍾源。如 kvm_clock,xen_clock

時鍾源相關操作:

  • 查看當前系統可用時鍾源
# cat /sys/devices/system/clocksource/clocksource0/available_clocksource
  • 查看當前使用的時鍾源
# cat /sys/devices/system/clocksource/clocksource0/current_clocksource
  • 修改時鍾源
# echo 'hpet' > /sys/devices/system/clocksource/clocksource0/current_clocksource

http://pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html

http://blog.sina.com.cn/s/blog_71d9aee40101gtuv.html

https://blog.csdn.net/dymloveyxp1314/article/details/10065223

http://news.mydrivers.com/1/273/273867_all.htm


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM