2014-08-30 74 views
2

這是一個性能相關的問題:字符串初始化和性能

repeatPrint1()的方法,我在循環使用前初始化一些String屬性null。 在repeatPrint2()方法中,我直接在循環內初始化了一些String屬性。

當我測試,結果是驚人的 - repeatPrint2()正在執行比repeatPrint1() 高達近2500條記錄,但repeatPrint1()啓動後進行比repeatPrint2()

任何人都可以解釋爲什麼發生這種情況? 在代碼審查中,我們可以採取什麼正確的方式?

代碼段:

public static void main(String[] args) throws Exception {  
    long startTime1 = System.nanoTime();  
    repeatPrint1(); 
    long estimatedTime1 = System.nanoTime() - startTime1; 
    long startTime2 = System.nanoTime(); 
    repeatPrint2(); 
    long estimatedTime2 = System.nanoTime() - startTime2; 
    System.out.println("estimatedTime1: " + estimatedTime1); 
    System.out.println("estimatedTime2: " + estimatedTime2); 
} 


private static void repeatPrint1() { 
    String name1 = null; 
    String name2 = null; 
    String name3 = null; 
    String name4 = null; 
    String name5 = null; 

    for(int x = 10; x < 2500; x = x + 1) {   
     name1 = "My Name is AAAA" + x; 
     name2 = "My Name is BBBB" + x; 
     name3 = "My Name is CCCC" + x; 
     name4 = "My Name is DDDD" + x; 
     name5 = "My Name is EEEE" + x; 

     System.out.print("name1 : " + name1); 
     System.out.print("name2 : " + name2); 
     System.out.print("name3 : " + name3); 
     System.out.print("name4 : " + name4); 
     System.out.print("name5 : " + name5); 

     System.out.println("value of x : " + x);   
    }  
} 


private static void repeatPrint2() {  
    for(int x = 10; x < 2500; x = x + 1) {   
     String Sname1 = "My Name is AAAA" + x; 
     String Sname2 = "My Name is BBBB" + x; 
     String Sname3 = "My Name is CCCC" + x; 
     String Sname4 = "My Name is DDDD" + x; 
     String Sname5 = "My Name is EEEE" + x; 

     System.out.print("Sname1 : " + Sname1); 
     System.out.print("Sname2 : " + Sname2); 
     System.out.print("Sname3 : " + Sname3); 
     System.out.print("Sname4 : " + Sname4); 
     System.out.print("Sname5 : " + Sname5); 

     System.out.println("value of x : " + x);   
    }  
} 
+3

嘗試測試'repeatPrint2'之前'repeatPrint1'。你可能會得到不同的結果。 :) – MAV 2014-08-30 10:14:37

+0

您可以討論您的測試環境,即機器,負載和運行次數嗎? – Manu 2014-08-30 10:15:41

+0

@MAV我打算說同樣的事情 – ControlAltDel 2014-08-30 10:15:57

回答

3

看一看這個question,其中介紹瞭如何建立一個良好的微基準。

兩個要點是:

  • 有一個預熱階段,你打電話給你的一些一萬次的方法。
    這樣JIT將緩存該方法。
  • 將會有一個區別,如果你打電話之前repeatPrint2 repeatPrint1()(),反之亦然

你與你的代碼獲得的結果將在任何運行不同。花一些時間來設定一個好的基準,以獲得有價值的結果。

+0

repeatPrint1()看起來不錯,當我單獨測試。 – user3924979 2014-08-30 10:23:30

+0

repeatPrint1()在我單獨測試時看起來不錯。有500條記錄,repeatPrint1()花了63869468納秒,但repeatPrint2()花了77103800納秒。所以當我們單獨和一起測試時,它的行爲會有所不同。其實我想知道2個JVM如何解釋。 – user3924979 2014-08-30 10:23:53

+0

那麼,關於什麼? – 2014-08-30 10:24:45

3

使用javap讀取字節碼並比較兩種方法轉換。

從您發佈的代碼中,我認爲它幾乎是相同的:編譯器應該將本地變量(如repeatPrint2)移動到方法的頂部。

您遇到的情況可能是JIT編譯器在運行中優化代碼。

我個人更喜歡在它使用的範圍而不是外部範圍聲明變量(例如:如repeatPrint2):這對於可讀性和重構來說更好。

0

主要原因是性能是一個隨機變量,因此,如果您有可靠的數據來詳細說明,您可能首先需要生成合理數量的數據點,以便您可以執行統計分析。

+0

我在Eclipse中測試過它,我檢查了不同數量的記錄並看到變化。我的問題是,當我編寫方法1後,性能與個人測試相比發生了巨大變化。 – user3924979 2014-08-30 10:33:10

+0

好的,但是你沒有運行任何熱身階段,以避免緩存造成的差異。此外,您沒有報告足夠的數據來得出關於性能的任何結論。您可能需要設置適當的基準,然後報告定量結果。沒有這個,任何結論都不會得到經驗證據的支持。 – Manu 2014-08-30 10:41:51

1

你給出的兩種方法基本相同。你的時機正在被拋棄,主要是因爲JVM沒有升溫;直到方法或循環重複10,000次以上,熱點纔會啓動。在經過那麼多次重複之後,你可能經常也會在特別的地方進行GC踢球,以及混合中的OS進程。

要解決基準測試中的這些問題中的一些問題,請將每種方法預熱10k次並重復幾次。對於操作系統噪音,請確保儘可能多地關閉其他許多應用程序和進程,並確保機器運行空閒。操作系統當然也可以進行調整,但現在讓我們把它作爲主題。下面是一些示例代碼,可以給你一個想法,我還增加了兩種方法來優化循環;只是爲了好玩;)

我包括我得到的底部的時機。要注意的事情是

  1. ,你包括開始改變,但要正常化很快是差不多的兩種方法
  2. 代碼的成本正在被調用到System.out相形見絀;換言之,io本身。證明我已經給出了另外兩種選擇,一種是對System.out進行更多的調用,另一種是減少調用。

estimatedTime1:17.27938毫秒

estimatedTime2:17.45852毫秒

estimatedTime3:100.61994毫秒

estimatedTime4:9.553329999999999毫秒

public class Foo { 
    public static void main(String[] args) throws Exception { 
     time1(); 
     time2(); 
     time3(); 
     time4(); 

     time1(); 
     time2(); 
     time3(); 
     time4(); 

     time1(); 
     time2(); 
     time3(); 
     time4(); 
    } 

    private static final int REPEAT = 100; 

    private static void time1() { 
     long startTime1 = System.nanoTime(); 
     for (int i=0; i<REPEAT; i++) { 
      repeatPrint1(); 
     } 
     long estimatedTime1 = System.nanoTime() - startTime1; 

     double dur = estimatedTime1/1000000.0/REPEAT; 
     System.out.println("estimatedTime1: " + dur); 
    } 

    private static void time2() { 
     long startTime1 = System.nanoTime(); 
     for (int i=0; i<REPEAT; i++) { 
      repeatPrint2(); 
     } 
     long estimatedTime1 = System.nanoTime() - startTime1; 

     double dur = estimatedTime1/1000000.0/REPEAT; 
     System.out.println("estimatedTime2: " + dur); 
    } 

    private static void time3() { 
     long startTime1 = System.nanoTime(); 
     for (int i=0; i<REPEAT; i++) { 
      repeatPrint3(); 
     } 
     long estimatedTime1 = System.nanoTime() - startTime1; 

     double dur = estimatedTime1/1000000.0/REPEAT; 
     System.out.println("estimatedTime3: " + dur); 
    } 

    private static void time4() { 
     long startTime1 = System.nanoTime(); 
     for (int i=0; i<REPEAT; i++) { 
      repeatPrint4(); 
     } 
     long estimatedTime1 = System.nanoTime() - startTime1; 

     double dur = estimatedTime1/1000000.0/REPEAT; 
     System.out.println("estimatedTime4: " + dur); 
    } 



    private static void repeatPrint1() { 

     String name1 = null; 
     String name2 = null; 
     String name3 = null; 
     String name4 = null; 
     String name5 = null; 
     for (int x = 10; x < 2500; x = x + 1) { 

      name1 = "My Name is AAAA" + x; 
      name2 = "My Name is BBBB" + x; 
      name3 = "My Name is CCCC" + x; 
      name4 = "My Name is DDDD" + x; 
      name5 = "My Name is EEEE" + x; 

      System.out.print("name1 : " + name1); 
      System.out.print("name2 : " + name2); 
      System.out.print("name3 : " + name3); 
      System.out.print("name4 : " + name4); 
      System.out.print("name5 : " + name5); 

      System.out.println("value of x : " + x); 

     } 

    } 


    private static void repeatPrint2() { 

     for (int x = 10; x < 2500; x = x + 1) { 

      String Sname1 = "My Name is AAAA" + x; 
      String Sname2 = "My Name is BBBB" + x; 
      String Sname3 = "My Name is CCCC" + x; 
      String Sname4 = "My Name is DDDD" + x; 
      String Sname5 = "My Name is EEEE" + x; 

      System.out.print("Sname1 : " + Sname1); 
      System.out.print("Sname2 : " + Sname2); 
      System.out.print("Sname3 : " + Sname3); 
      System.out.print("Sname4 : " + Sname4); 
      System.out.print("Sname5 : " + Sname5); 

      System.out.println("value of x : " + x); 

     } 
    } 



    private static void repeatPrint3() { 
     for (int x = 10; x < 2500; x = x + 1) { 
      pr1(1, x, "AAAA"); 
      pr1(2, x, "BBBB"); 
      pr1(3, x, "CCCC"); 
      pr1(4, x, "DDDD"); 
      pr1(5, x, "EEEE"); 
      xr1(x); 
     } 
    } 
    private static void pr1(int i, int x, String r) { 
     System.out.print("Sname"); 
     System.out.print('0'+i); 
     System.out.print(" : "); 
     System.out.print("My Name is "); 
     System.out.print(r); 
     System.out.println(Integer.toString(x)); 
    } 
    private static void xr1(int x) { 
     System.out.print("value of x : "); 
     System.out.println(Integer.toString(x)); 
    } 

    private static void repeatPrint4() { 
     StringBuilder buf = new StringBuilder(400); 

     for (int x = 10; x < 2500; x = x + 1) { 
      pr2(buf, 1, x, "AAAA"); 
      pr2(buf, 2, x, "BBBB"); 
      pr2(buf, 3, x, "CCCC"); 
      pr2(buf, 4, x, "DDDD"); 
      pr2(buf, 5, x, "EEEE"); 
      xr2(buf, x); 

      System.out.print(buf.toString()); 

      buf.setLength(0); 
     } 
    } 

    private static void pr2(StringBuilder buf, int i, int x, String r) { 
     buf.append("Sname"); 
     buf.append('0' + i); 
     buf.append(" : "); 
     buf.append("My Name is "); 
     buf.append(r); 
     buf.append(Integer.toString(x)); 
     buf.append("\n"); 
    } 
    private static void xr2(StringBuilder buf, int x) { 
     buf.append("value of x : "); 
     buf.append(Integer.toString(x)); 
     buf.append("\n"); 
    } 

} 
+0

我喜歡你的repeatPrint4,但你應該添加一個例子,在循環之後打印緩衝區(並且沒有setLength)。 – NoDataFound 2014-08-30 12:04:28

+0

@NoDataFound這個例子有你需要改變,去做。上面給出的3和4的目的是爲了強調調用system.out.print的成本;它使其他一切變得渺小。至於把setLength移出來,那麼這將徹底改變你正在分析的原始事物的記憶行爲。它會運行得更慢,並可能只是內存不足:) – 2014-08-30 13:56:33