日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

局部变量竟然比全局变量快 5 倍?

發布時間:2025/3/11 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 局部变量竟然比全局变量快 5 倍? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這是我的第?201?期分享

作者 | 王磊

來源 | Java中文社群(ID:javacn666)

轉載請聯系授權(微信ID:GG_Stone)

嘍,大家好,磊哥的性能優化篇又來了!

其實寫這個性能優化類的文章初衷也很簡單,第一:目前市面上沒有太好的關于性能優化的系列文章,包括一些付費的文章;第二:我需要寫一些和別人不同的知識點,比如大家都去寫 SpringBoot 了,那我就不會把重點全部放在 SpringBoot 上。而性能優化方面的文章又比較少,因此這就是我寫它的理由。

至于能不能用上?是不是剛需?我想每個人都有自己的答案。就像一個好的劍客,終其一生都會對寶劍癡迷,我相信讀到此文的你也是一樣。

回到今天的主題,這次我們來評測一下局部變量和全局變量的性能差異,首先我們先在項目中先添加 Oracle 官方提供的 JMH(Java Microbenchmark Harness,JAVA 微基準測試套件)測試框架,配置如下:

<!--?https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core?--> <dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>{version}</version> </dependency>

然后編寫測試代碼:

import?org.openjdk.jmh.annotations.*; import?org.openjdk.jmh.runner.Runner; import?org.openjdk.jmh.runner.RunnerException; import?org.openjdk.jmh.runner.options.Options; import?org.openjdk.jmh.runner.options.OptionsBuilder;import?java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime)?//?測試完成時間 @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations?=?2,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?預熱?2?輪,每次?1s @Measurement(iterations?=?5,?time?=?3,?timeUnit?=?TimeUnit.SECONDS)?//?測試?5?輪,每次?3s @Fork(1)?//?fork?1?個線程 @State(Scope.Thread)?//?每個測試線程一個實例 public?class?VarOptimizeTest?{char[]?myChars?=?("Oracle?Cloud?Infrastructure?Low?data?networking?fees?and?"?+"automated?migration?Oracle?Cloud?Infrastructure?platform?is?built?for?"?+"enterprises?that?are?looking?for?higher?performance?computing?with?easy?"?+"migration?of?their?on-premises?applications?to?the?Cloud.").toCharArray();public?static?void?main(String[]?args)?throws?RunnerException?{//?啟動基準測試Options?opt?=?new?OptionsBuilder().include(VarOptimizeTest.class.getSimpleName())?//?要導入的測試類.build();new?Runner(opt).run();?//?執行測試}@Benchmarkpublic?int?globalVarTest()?{int?count?=?0;for?(int?i?=?0;?i?<?myChars.length;?i++)?{if?(myChars[i]?==?'c')?{count++;}}return?count;}@Benchmarkpublic?int?localityVarTest()?{char[]?localityChars?=?myChars;int?count?=?0;for?(int?i?=?0;?i?<?localityChars.length;?i++)?{if?(localityChars[i]?==?'c')?{count++;}}return?count;} }

其中 globalVarTest?方法使用的是全局變量 myChars?進行循環遍歷的,而 localityVarTest?方法使用的是局部變量 localityChars?來進行遍歷循環的,使用 JMH 測試的結果如下:

咦,什么鬼?這兩個方法的性能不是差不多嘛!為毛,你說差 5 倍?


CPU Cache

上面的代碼之所以性能差不多其實是因為,全局變量?myChars?被 CPU 緩存了,每次我們查詢時不會直接從對象的實例域(對象的實際存儲結構)中查詢的,而是直接從 CPU 的緩存中查詢的,因此才有上面的結果。

為了還原真實的性能(局部變量和全局變量),因此我們需要使用 volatile?關鍵來修飾?myChars 全局變量,這樣 CPU 就不會緩存此變量了, volatile?原本的語義是禁用 CPU 緩存的,我們修改的代碼如下:

import?org.openjdk.jmh.annotations.*; import?org.openjdk.jmh.runner.Runner; import?org.openjdk.jmh.runner.RunnerException; import?org.openjdk.jmh.runner.options.Options; import?org.openjdk.jmh.runner.options.OptionsBuilder;import?java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime)?//?測試完成時間 @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations?=?2,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?預熱?2?輪,每次?1s @Measurement(iterations?=?5,?time?=?3,?timeUnit?=?TimeUnit.SECONDS)?//?測試?5?輪,每次?3s @Fork(1)?//?fork?1?個線程 @State(Scope.Thread)?//?每個測試線程一個實例 public?class?VarOptimizeTest?{volatile?char[]?myChars?=?("Oracle?Cloud?Infrastructure?Low?data?networking?fees?and?"?+"automated?migration?Oracle?Cloud?Infrastructure?platform?is?built?for?"?+"enterprises?that?are?looking?for?higher?performance?computing?with?easy?"?+"migration?of?their?on-premises?applications?to?the?Cloud.").toCharArray();public?static?void?main(String[]?args)?throws?RunnerException?{//?啟動基準測試Options?opt?=?new?OptionsBuilder().include(VarOptimizeTest.class.getSimpleName())?//?要導入的測試類.build();new?Runner(opt).run();?//?執行測試}@Benchmarkpublic?int?globalVarTest()?{int?count?=?0;for?(int?i?=?0;?i?<?myChars.length;?i++)?{if?(myChars[i]?==?'c')?{count++;}}return?count;}@Benchmarkpublic?int?localityVarTest()?{char[]?localityChars?=?myChars;int?count?=?0;for?(int?i?=?0;?i?<?localityChars.length;?i++)?{if?(localityChars[i]?==?'c')?{count++;}}return?count;} }

最終的測試結果是:


從上面的結果可以看出,局部變量的性能比全局變量的性能快了大約?5.02 倍

至于為什么局部變量會比全局變量快?咱們稍后再說,我們先來聊聊 CPU 緩存的事。

在計算機系統中,CPU 緩存(CPU Cache)是用于減少處理器訪問內存所需平均時間的部件。在金字塔式存儲體系中它位于自頂向下的第二層,僅次于 CPU 寄存器,如下圖所示:


CPU 緩存的容量遠小于內存,但速度卻可以接近處理器的頻率。當處理器發出內存訪問請求時,會先查看緩存內是否有請求數據。如果存在(命中),則不經訪問內存直接返回該數據;如果不存在(失效),則要先把內存中的相應數據載入緩存,再將其返回處理器。

CPU 緩存可以分為一級緩存(L1),二級緩存(L2),部分高端 CPU 還具有三級緩存(L3),這三種緩存的技術難度和制造成本是相對遞減的,所以其容量也是相對遞增的。當 CPU 要讀取一個數據時,首先從一級緩存中查找,如果沒有找到再從二級緩存中查找,如果還是沒有就從三級緩存或內存中查找。

以下是各級緩存和內存響應時間的對比圖:

(圖片來源:cenalulu)

從上圖可以看出內存的響應速度要比 CPU 緩存慢很多


局部變量為什么快?

要理解為什么局部變量會比全局變量快這個問題,我們只需要使用 javac?把他們編譯成字節碼就可以找到原因了,編譯的字節碼如下:

javap?-c?VarOptimize 警告:?文件?./VarOptimize.class?不包含類?VarOptimize Compiled?from?"VarOptimize.java" public?class?com.example.optimize.VarOptimize?{char[]?myChars;public?com.example.optimize.VarOptimize();Code:0:?aload_01:?invokespecial?#1??????????????????//?Method?java/lang/Object."<init>":()V4:?aload_05:?ldc???????????#7??????????????????//?String?Oracle?Cloud?Infrastructure?Low?data?networking?fees?and?automated?migration?Oracle?Cloud?Infrastructure?platform?is?built?for?enterprises?that?are?looking?for?higher?performance?computing?with?easy?migration?of?their?on-premises?applications?to?the?Cloud.7:?invokevirtual?#9??????????????????//?Method?java/lang/String.toCharArray:()[C10:?putfield??????#15?????????????????//?Field?myChars:[C13:?returnpublic?static?void?main(java.lang.String[]);Code:0:?new???????????#16?????????????????//?class?com/example/optimize/VarOptimize3:?dup4:?invokespecial?#21?????????????????//?Method?"<init>":()V7:?astore_18:?aload_19:?invokevirtual?#22?????????????????//?Method?globalVarTest:()V12:?aload_113:?invokevirtual?#25?????????????????//?Method?localityVarTest:()V16:?returnpublic?void?globalVarTest();Code:0:?iconst_01:?istore_12:?iconst_03:?istore_24:?iload_25:?aload_06:?getfield??????#15?????????????????//?Field?myChars:[C9:?arraylength10:?if_icmpge?????3313:?aload_014:?getfield??????#15?????????????????//?Field?myChars:[C17:?iload_218:?caload19:?bipush????????9921:?if_icmpne?????2724:?iinc??????????1,?127:?iinc??????????2,?130:?goto??????????433:?returnpublic?void?localityVarTest();Code:0:?aload_01:?getfield??????#15?????????????????//?Field?myChars:[C4:?astore_15:?iconst_06:?istore_27:?iconst_08:?istore_39:?iload_310:?aload_111:?arraylength12:?if_icmpge?????3215:?aload_116:?iload_317:?caload18:?bipush????????9920:?if_icmpne?????2623:?iinc??????????2,?126:?iinc??????????3,?129:?goto??????????932:?return }

其中關鍵的信息就在 getfield?關鍵字上,getfield?在此處的語義是從堆上獲取變量,從上述的字節碼可以看出 globalVarTest?方法在循環的內部每次都通過?getfield?關鍵字從堆上獲取變量,而 localityVarTest?方法并沒有使用?getfield 關鍵字,而是使用了出棧操作來進行業務處理,而從堆中獲取變量比出棧操作要慢很多,因此使用全局變量會比局部變量慢很多。關于堆、棧的內容關注公眾號「Java中文社群」我在后面的 JVM 優化的章節會單獨講解。

關于緩存

有人可能會說無所謂,反正使用全局變量會使用 CPU Cache,這樣性能也和局部變量差不多,那我就隨便用吧,反正也差不多。

但磊哥的建議是,能用局部變量的絕不使用全局變量,因為 CPU 緩存有以下 3 個問題:

  • CPU Cache 采用的是 LRU 和 Random 的清除算法,不常使用的緩存和隨機抽取一部分緩存會被刪除掉,如果正好是你用的那個全局變量呢?

  • CPU Cache 有緩存命中率的問題,也就是有一定的幾率會訪問不到緩存;

  • 部分 CPU 只有兩級緩存(L1 和 L2),因此可以使用的空間是有限的。

  • 綜上所述,我們不能把程序的執行性能完全托付給一個不那么穩定的系統硬件,所以能用局部變量堅決不要使用全局變量

    關鍵點:編寫適合你的代碼,在性能、可讀性和實用性之間,找到屬于你的平衡點!

    總結

    本文我們講了局部變量的和全局變量的區別,如果使用全局變量會用 getfield?關鍵字從堆中獲取變量,而局部變量則是通過出棧來獲取變量的,因為出棧操作要比堆操作快很多,因此局部變量操作也會比全局變量快很多,所以建議你使用局部變量而不是全局變量。

    高手之間對決,比拼的就是細節。

    最后的話原創不易,覺得有幫助,點個「在看」讓我知道,謝謝你! 往期推薦

    if快還是switch快?解密switch背后的秘密

    本來想用“{{”秀一波,結果卻導致了內存溢出!

    覺得本文有幫助,點擊“在看”鼓勵下我吧!

    總結

    以上是生活随笔為你收集整理的局部变量竟然比全局变量快 5 倍?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。