自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java 自動裝箱性能

開發(fā) 后端
為了查看代碼熱路徑(hot path)上的結(jié)果,JMH集成了Linux工具perf,可以查看最熱代碼塊的JIT編譯結(jié)果。(要想查看匯編代碼,需要安裝hsdis插件。我在 AUR上提供了下載,Arch用戶可以直接獲取。)在JMH命令行添加 -prof perfasm 命令,就可以看到結(jié)果:

Java 的基本數(shù)據(jù)類型(int、double、 char)都不是對象。但由于很多Java代碼需要處理的是對象(Object),Java給所有基本類型提供了包裝類(Integer、Double、Character)。有了自動裝箱,你可以寫如下的代碼

[[150602]]

  1. Character boxed = 'a'
  2. char unboxed = boxed; 

編譯器自動將它轉(zhuǎn)換為

 

  1. Character boxed = Character.valueOf('a'); 
  2. char unboxed = boxed.charValue(); 

然而,Java虛擬機(jī)不是每次都能理解這類過程,因此要想得到好的系統(tǒng)性能,避免不必要的裝箱很關(guān)鍵。這也是 OptionalInt 和 IntStream 等特殊類型存在的原因。在這篇文章中,我將概述JVM很難消除自動裝箱的一個原因。

實(shí)例

例如,我們想要計算任意一類數(shù)據(jù)的編輯距離(Levenshtein距離),只要這些數(shù)據(jù)可以被看作一個序列:

 

  1. public class Levenshtein{ 
  2. private final Function> asList; 
  3.  
  4. public Levenshtein(Function> asList) { 
  5. this.asList = asList; 
  6.  
  7. public int distance(T a, T b) { 
  8. // Wagner-Fischer algorithm, with two active rows 
  9.  
  10. List aList = asList.apply(a); 
  11. List bList = asList.apply(b); 
  12.  
  13. int bSize = bList.size(); 
  14. int[] row0 = new int[bSize + 1]; 
  15. int[] row1 = new int[bSize + 1]; 
  16.  
  17. for (int i = 0; i row0[i] = i; 
  18.  
  19. for (int i = 0; i < bSize; ++i) { 
  20. U ua = aList.get(i); 
  21. row1[0] = row0[0] + 1
  22.  
  23. for (int j = 0; j < bSize; ++j) { 
  24. U ub = bList.get(j); 
  25. int subCost = row0[j] + (ua.equals(ub) ? 0 : 1); 
  26. int delCost = row0[j + 1] + 1
  27. int insCost = row1[j] + 1
  28. row1[j + 1] = Math.min(subCost, Math.min(delCost, insCost)); 
  29.  
  30. int[] temp = row0; 
  31. row0 = row1; 
  32. row1 = temp; 
  33.  
  34. return row0[bSize]; 

只要兩個對象可以被看作List,這個類就可以計算它們的編輯距離。如果想計算String類型的距離,那么就需要把String轉(zhuǎn)變?yōu)長ist類型:

 

  1. public class StringAsList extends AbstractList{ 
  2. private final String str; 
  3.  
  4. public StringAsList(String str) { 
  5. this.str = str; 
  6.  
  7. @Override 
  8. public Character get(int index) { 
  9. return str.charAt(index); // Autoboxing! } 
  10.  
  11. @Override 
  12. public int size() { 
  13. return str.length(); 
  14.  
  15. ... 
  16.  
  17. Levenshteinlev = new Levenshtein<>(StringAsList::new); 
  18. lev.distance("autoboxing is fast""autoboxing is slow"); // 4 

由于Java泛型的實(shí)現(xiàn)方式,不能有List類型,所以要提供List和裝箱操作。(注:Java10中,這個限制也許會被取消。)

為了查看代碼熱路徑(hot path)上的結(jié)果,JMH集成了Linux工具perf,可以查看最熱代碼塊的JIT編譯結(jié)果。(要想查看匯編代碼,需要安裝hsdis插件。我在 AUR上提供了下載,Arch用戶可以直接獲取。)在JMH命令行添加 -prof perfasm 命令,就可以看到結(jié)果:

為了測試 distance() 方法的性能,需要做基準(zhǔn)測試。Java中微基準(zhǔn)測試很難保證準(zhǔn)確,但幸好OpenJDK提供了JMH(Java Microbenchmark Harness),它可以幫我們解決大部分難題。如果感興趣的話,推薦大家閱讀文檔和實(shí)例;它會很吸引你。以下是基準(zhǔn)測試:

 

  1. @State(Scope.Benchmark) 
  2. public class MyBenchmark { 
  3. private Levenshtein lev = new Levenshtein<>(StringAsList::new); 
  4.  
  5. @Benchmark 
  6. @BenchmarkMode(Mode.AverageTime) 
  7. @OutputTimeUnit(TimeUnit.NANOSECONDS) 
  8. public int timeLevenshtein() { 
  9. return lev.distance("autoboxing is fast""autoboxing is slow"); 

(返回方法的結(jié)果,這樣JMH就可以做一些操作讓系統(tǒng)認(rèn)為返回值會被使用到,防止冗余代碼消除影響了結(jié)果。)

以下是結(jié)果:

  1. $ java -jar target/benchmarks.jar -f 1 -wi 8 -i 8 
  2. # JMH 1.10.2 (released 3 days ago) 
  3. # VM invoker: /usr/lib/jvm/java-8-openjdk/jre/bin/java 
  4. # VM options: 
  5. # Warmup: 8 iterations, 1 s each 
  6. # Measurement: 8 iterations, 1 s each 
  7. # Timeout: 10 min per iteration 
  8. # Threads: 1 thread, will synchronize iterations 
  9. # Benchmark mode: Average time, time/op 
  10. # Benchmark: com.tavianator.boxperf.MyBenchmark.timeLevenshtein 
  11.  
  12. # Run progress: 0.00% complete, ETA 00:00:16 
  13. # Fork: 1 of 1 
  14. # Warmup Iteration 11517.495 ns/op 
  15. # Warmup Iteration 21503.096 ns/op 
  16. # Warmup Iteration 31402.069 ns/op 
  17. # Warmup Iteration 41480.584 ns/op 
  18. # Warmup Iteration 51385.345 ns/op 
  19. # Warmup Iteration 61474.657 ns/op 
  20. # Warmup Iteration 71436.749 ns/op 
  21. # Warmup Iteration 81463.526 ns/op 
  22. Iteration 11446.033 ns/op 
  23. Iteration 21420.199 ns/op 
  24. Iteration 31383.017 ns/op 
  25. Iteration 41443.775 ns/op 
  26. Iteration 51393.142 ns/op 
  27. Iteration 61393.313 ns/op 
  28. Iteration 71459.974 ns/op 
  29. Iteration 81456.233 ns/op 
  30.  
  31. Result "timeLevenshtein"
  32. 1424.461 ±(99.9%) 59.574 ns/op [Average] 
  33. (min, avg, max) = (1383.0171424.4611459.974), stdev = 31.158 
  34. CI (99.9%): [1364.8871484.034] (assumes normal distribution) 
  35.  
  36. # Run complete. Total time: 00:00:16 
  37.  
  38. Benchmark Mode Cnt Score Error Units 
  39. MyBenchmark.timeLevenshtein avgt 8 1424.461 ± 59.574 ns/op 

分析

為了查看代碼熱路徑(hot path)上的結(jié)果,JMH集成了Linux工具perf,可以查看最熱代碼塊的JIT編譯結(jié)果。(要想查看匯編代碼,需要安裝hsdis插件。我在 AUR上提供了下載,Arch用戶可以直接獲取。)在JMH命令行添加 -prof perfasm 命令,就可以看到結(jié)果:

  1. $ java -jar target/benchmarks.jar -f 1 -wi 8 -i 8 -prof perfasm 
  2. ... 
  3. cmp $0x7f,%eax 
  4. jg 0x00007fde989a6148 ;*if_icmpgt 
  5. ; - java.lang.Character::valueOf@3 (line 4570
  6. ; - com.tavianator.boxperf.StringAsList::get@8 (line 14
  7. ; - com.tavianator.boxperf.StringAsList::get@2; (line 5
  8. ; - com.tavianator.boxperf.Levenshtein::distance@121 (line 32
  9. cmp $0x80,%eax 
  10. jae 0x00007fde989a6103 ;*aaload 
  11. ; - java.lang.Character::valueOf @ 10 (line 4571
  12. ; - com.tavianator.boxperf.StringAsList::get@8 (line 14
  13. ; - com.tavianator.boxperf.StringAsList::get @ 2 (line 5
  14. ; - com.tavianator.boxperf.Levenshtein::distance@121 (line 32
  15. ... 

輸出內(nèi)容很多,但上面的一點(diǎn)內(nèi)容就說明裝箱沒有被優(yōu)化。為什么要和0x7f/0×80的內(nèi)容做比較呢?原因在于Character.valueOf()的取值來源:

 

  1. private static class CharacterCache { 
  2. private CharacterCache(){} 
  3.  
  4. static final Character cache[] = new Character[127 + 1]; 
  5.  
  6. static { 
  7. for (int i = 0; i < cache.length; i++) 
  8. cache[i] = new Character((char)i); 
  9.  
  10. public static Character valueOf(char c) { 
  11. if (c return CharacterCache.cache[(int)c]; 
  12. return new Character(c); 

可以看出,Java語法標(biāo)準(zhǔn)規(guī)定前127個char的Character對象放在緩沖池中,Character.valueOf()的結(jié)果在其中 時,直接返回緩沖池的對象。這樣做的目的是減少內(nèi)存分配和垃圾回收,但在我看來這是過早的優(yōu)化。而且它妨礙了其他優(yōu)化。JVM無法確定 Character.valueOf(c).charValue() == c,因?yàn)樗恢谰彌_池的內(nèi)容。所以JVM從緩沖池中取了一個Character對象并讀取它的值,結(jié)果得到的就是和 c 一樣的內(nèi)容。

解決方法

解決方法很簡單:

  1. @ @ -11,7 +11,7 @ @ public class StringAsList extends AbstractList { 
  2.  
  3. @Override 
  4. public Character get(int index) { 
  5. return str.charAt(index); // Autoboxing! 
  6. return new Character(str.charAt(index)); 

@Override

用顯式的裝箱代替自動裝箱,就避免了調(diào)用Character.valueOf(),這樣JVM就很容易理解代碼:

  1. private final char value; 
  2.  
  3. public Character(char value) { 
  4. this.value = value; 
  5.  
  6. public char charValue() { 
  7. return value; 

雖然代碼中加了一個內(nèi)存分配,但JVM能理解代碼的意義,會直接從String中獲取char字符。性能提升很明顯:

 

  1. $ java -jar target/benchmarks.jar -f 1 -wi 8 -i 8 
  2. ... 
  3. # Run complete. Total time: 00:00:16 
  4.  
  5. Benchmark Mode Cnt Score Error Units 
  6. MyBenchmark.timeLevenshtein avgt 8 1221.151 ± 58.878 ns/op 

速度提升了14%。用 -prof perfasm 命令可以顯示,改進(jìn)以后是直接從String中拿到char值并在寄存器中比較的:

movzwl 0x10(%rsi,%rdx,2),%r11d ;*caload
; - java.lang.String::charAt@27 (line 648)
; - com.tavianator.boxperf.StringAsList::get@9 (line 14)
; - com.tavianator.boxperf.StringAsList::get @ 2 (line 5)
; - com.tavianator.boxperf.Levenshtein::distance@121 (line 32)
cmp %r11d,%r10d
je 0x00007faa8d404792 ;*if_icmpne
; - java.lang.Character::equals@18 (line 4621)
; - com.tavianator.boxperf.Levenshtein::distance@137 (line 33)

總結(jié)

裝箱是HotSpot的一個弱項(xiàng),希望它能做到越來越好。它應(yīng)該多利用裝箱類型的語義,消除裝箱操作,這樣以上的解決辦法就沒有必要了。

以上的基準(zhǔn)測試代碼都可以在GitHub上訪問。

責(zé)任編輯:王雪燕 來源: ImportNew
相關(guān)推薦

2012-03-26 11:32:45

Java

2015-09-02 10:12:54

Java自動裝箱拆箱

2018-09-05 15:51:25

Java自動拆裝箱

2009-06-08 22:03:37

裝箱問題Java

2020-11-02 13:06:42

Java裝箱拆箱

2009-08-26 03:39:00

C#裝箱和拆箱

2013-12-06 14:52:49

性能評價模型分析WEB系統(tǒng)

2020-03-27 20:22:53

數(shù)據(jù)集裝箱網(wǎng)絡(luò)

2009-08-28 11:22:11

C#裝箱和拆箱

2021-01-24 11:46:26

自動化Web 優(yōu)化

2012-05-11 10:11:55

Java游戲維護(hù)

2019-10-30 16:03:48

JavaJava虛擬機(jī)數(shù)據(jù)庫

2011-12-15 09:55:47

javanio

2015-06-04 09:59:01

數(shù)據(jù)中心集裝箱

2021-09-06 14:30:34

C#裝箱拆箱

2015-06-10 14:07:27

數(shù)據(jù)中心

2011-11-28 11:04:53

數(shù)據(jù)中心云立方云計算

2009-08-06 15:40:11

C#裝箱和拆箱

2024-09-06 07:55:42

2011-05-07 11:08:44

惠普冷卻系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號