刷題過程中遇到的一些時間復雜度相同,但是常數因子的差距導致的性能差距,遇到持續更新
枚舉 VS contains
例如:判斷一個字符是不是元音
法一:
if(ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u')
法二:
Set<Character> set = new HashSet<>();
set.add('a');
set.add('e');
set.add('i');
set.add('o');
set.add('u');if(set.contains(ch))
結果顯示法一更快
? ? ? ? 在指令并行的基礎上,多個條件判斷可以并行執行
法二的問題在于:
? ? ? ? contains()是一個動態方法調用
? ? ? ? 涉及哈希函數計算、char-Character的裝箱拆箱
? ? ? ? java的虛擬方法調用會帶來額外的開銷,方法調用會產生棧幀的壓入彈出
? ? ? ? 對CPU分支預測不友好,因為大部分都不是元音,無法有效利用CPU的預測機制
什么時候用contains?
1.要比較的內容是動態變化的
2.元素很多,手寫條件判斷太繁瑣
3.代碼可讀性和可維護性優先于極致性能
【題外話】CPU預測機制:CPU在執行指令的時候是按照流水線方式工作的,希望盡可能多地并行處理多個指令,但是遇到if分支的時候,CPU不知道應該走哪一分支,所以會導致流水線停頓,《分支預測》就是CPU根據歷史行為猜測程序下一步會走哪個分支,從而提前執行那部分代碼,減少等待時間。也就是會根據過去的行為預測未來,預測錯了就退回正確路徑繼續執行,預測對了就節省大量時間
【極致優化】
boolean[] isVowel = new boolean[128];
isVowel['a'] = true;
isVowel['e'] = true;
isVowel['i'] = true;
isVowel['o'] = true;
isVowel['u'] = true;// 使用時:
if (isVowel[c]) {// 是元音
}
while VS for
法一:
while(r < s.length()){...r++;
}
法二:
for(int i = 0 ; i < ss.length ; i++ )
本質上雖然是一樣的,但是while循環不如for循環直觀,容易造成不必要的分支判斷,編譯器對for的優化更好(向量化)
for更適合已知循環次數的情況,且控制變量聲明、條件判斷、步進操作都在一行可讀性強
編譯器會對for循環做一系列優化:
? ? ? ? 向量化:如果循環體中是可并行計算的,JVM/JIT會吧這些操作向量化加速執行
? ? ? ? 循環展開:編譯器會自動將某些for循環展開為多個重復的操作,減少跳轉指令的開銷
for循環CPU會更容易預測,因為for循環的迭代次數往往是固定的或可預判的,分支預測器能更好的工作
什么時候用while:
循環次數不確定
依賴外部狀態變化退出循環
實現隊列、棧、事件驅動等結構
簡單的“直到滿足條件才退出”邏輯
char[] VS charAt
將字符串轉成字符數據可以避免每次都調用方法,可以提高訪問效率
charAt是個方法調用、每次調用都需要進行邊界檢查
char[]提高緩存局部性,編譯器對數組訪問的優化更好,但是有額外的內存消耗
什么時候用charAt()
字符串很長但是只需要訪問少數幾個字符,節省內存