love fish大鵬一曰同風起,扶搖直上九萬里

          常用鏈接

          統計

          積分與排名

          friends

          link

          最新評論

          關于聲明變量的性能問題 (轉)

          開啟一個新的問題(關于聲明變量的性能問題) (問題結束)

          在我們編程的過程中經常會遇到這樣的問題。
          for?(int?i=0;i<n;i++){
          ????String?str?
          =?//
          }

          String?str?
          =?null;
          for(int?i=0;i<n;i++){
          ????str?
          =?//
          }



          在印象中一直認為方法二的性能好于方法一,但是差距應該很小。但因為一位別人的回文說方法一極大的影響了性能,所以想寫個例子證明一下相差很小。例子如下:
          public?class?TestOt?{
          ????
          public?static?void?main(String[]?args)?{
          ????????
          long?n=1000000000;
          ????????
          long?start?=?System.currentTimeMillis();
          ????????test2(n);
          ????????
          long?end?=?System.currentTimeMillis();
          ????????System.out.println(end
          -start);
          ????}

          ????
          public?static?void?test1(long?n){
          ????????
          for?(int?i=0;i<n;i++){
          ????????????String?str?
          =?"";
          ????????}

          ????}

          ????
          public?static?void?test2(long?n){
          ????????String?str?
          =?null;
          ????????
          for?(int?i=0;i<n;i++){
          ????????????str?
          =?"";
          ????????}

          ????}

          }
          測試的結果是當n=10億次的時候差距是1秒,所以說差距應該是很小的,符合我原始的記憶,但是另一個問題來了,測試的結果是
          方法一:3300毫秒左右
          方法二:4300毫秒左右
          結果剛好相反,于是更改方法

          public?class?TestOt?{
          ????
          public?static?void?main(String[]?args)?{
          ????????
          long?n=1000000000;
          ????????
          long?start?=?System.currentTimeMillis();
          ????????test1(n);
          ????????
          long?end?=?System.currentTimeMillis();
          ????????System.out.println(end
          -start);
          ????}

          ????
          public?static?void?test1(long?n){
          ????????
          for?(int?i=0;i<n;i++){
          ????????????String?str?
          =?null;
          ????????}

          ????}

          ????
          public?static?void?test2(long?n){
          ????????String?str?
          =?null;
          ????????
          for?(int?i=0;i<n;i++){
          ????????}

          ????}

          }

          結果依舊。

          沒辦法,取得字節碼,對比
          public?class?TestOt?extends?java.lang.Object{
          public?TestOt();
          ??Code:
          ???
          0:???aload_0
          ???
          1:???invokespecial???#8;?//Method?java/lang/Object."<init>":()V
          ???4:???return

          public?static?void?test1(int);
          ??Code:
          ???
          0:???iconst_0
          ???
          1:???istore_1
          ???
          2:???goto????10???
          ???
          5:???aconst_null
          ???
          6:???astore_2
          ???
          7:???iinc????1,?1
          ???
          10:??iload_1???
          ???
          11:??iload_0
          ???
          12:??if_icmplt???????5
          ???
          15:??return

          public?static?void?test2(int);
          ??Code:
          ???
          0:???aconst_null
          ???
          1:???astore_1
          ???
          2:???iconst_0
          ???
          3:???istore_2
          ???
          4:???goto????10???
          ???
          7:???iinc????2,?1
          ???
          10:??iload_2???
          ???
          11:??iload_0??
          ???
          12:??if_icmplt???????7
          ???
          15:??return
          }

          結果是感覺還是應該是方法二快,那為什么反而方法一快了1秒左右呢?
          不得而知,現在我個人猜測的想法是可能有兩種情況:
          1,JLS的底層定義決定的,有什么特殊的優化?
          2,因為方法二比方法一雖然少了在循環中的部分,但是引用的聲明周期反而是更長了,是否因為引用存在造成了方法二的棧操作消耗了大部分時間?
          猜想一有待于JLS文檔的查閱,我會在有空的時候查詢,猜想二正在想辦法證明。
          看文章的朋友,如果誰了解麻煩指點一下,是我的測試方法寫的有問題,還是別的原因,謝謝。

          最后:問題已經基本了解了原因,見回復中的討論,謝謝--daydream 的幫忙。


          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 03:29 dreamstone

          最開始的測試是在Eclipse中測試的,怕是eclipse的問題,在控制臺下也做了測試,雖然得到的數字有微小偏差,但依然是方法一比方法二快1秒左右。問題繼續。。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 06:10 JonneyQuest

          據說是因為局部變量放在堆棧中的原因。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 09:26 daydream

          在我的機器上測試,兩者可以看作是一樣快的。對每一個方法運行多次,結果也會稍有偏差,第一個方法:3375(次數比較多)、3390。
          第二個方法:3375(次數比較多)、3390、3391、3406。

          對于下面2段代碼來說:
          for (int i=0;i<n;i++){
          String str = //
          }
          String str = null;
          for(int i=0;i<n;i++){
          str = //
          }
          區別只是str變量的作用域不同---意味著:代碼1的str變量的偏移位置在出了循環的作用域以后,可以被分配給下一個出現的局部變量,而代碼2str的位置會一直占有,直到方法結束。

          之所以,有人感覺代碼2快,我想是一種錯覺吧,就是以為代碼1會在循環中重復聲明變量str--實際上不是這樣。

          另外,JLS是什么東東???回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題)[未登錄] 2007-02-11 10:32 dreamstone

          to:JonneyQuest
          詳細說說?
          ??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題)[未登錄] 2007-02-11 10:36 dreamstone

          to:daydream
          你怎么運行的,不是兩個一塊調用的吧,如果是用循環多次調用求平均值,活著兩個一塊調用是不準的。如果你是循環調用,活著同時調用,你可以把兩個函數的調用順序換一下,會有較大的差距。
          JLS=The Java Language Specification
          講述的是Java語言的特性,很多東西C++和Java是不同的,JLS中有描述。例如lazyloading的單態在Java中是不可實現的,這個就因為Java的優化造成的,通過JLS可以查到。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 12:40 daydream

          沒有,我是分開運行的,直接運行你的代碼。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 13:55 sinoly

          這里問題大了。創建對象第一忌:不要在循環體中創建對象。這種做法會在內存中保存N份這個對象的引用會浪費大量的內存空間(雖說內存便宜,可以進行硬件升級),同時JVM的GC機制會因為這些無謂的對象做大量的回收工作,系統不慢都不行呀
          .........................................................................................

          很簡單的一個道理,你用String對象根本看不出效果。如果你換成個自定義對象或者圖形對象呢?你的第一種做法會在堆內存中生成大量的垃圾對象,這些對象首先占用內存,二則在對于速度的影響上它不會馬上體現出來(畢竟在內存夠用的情況下無法體現),一旦堆內存中的eden area滿了,GC機制開始起作用了那么你就會覺得你的程序速度狂降。。。。
          所以說,第二種做法才是王道!

          呵呵,剛剛寫了一篇關于java優化編程的文字,希望可以提供幫助
          http://www.aygfsteel.com/sinoly/archive/2007/02/11/99205.html??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 14:05 sinoly

          相對而言,性能的影響并不只是這段代碼的執行速度。需要考慮在JVM種它的處理方式,以及這種方式對資源占用的情況。很多性能問題都是在日積月累中體現的。只是丂一條語句所謂的執行速度來判斷效率,個人感覺很不合理??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 17:05 daydream

          to sinoly :

          “創建對象第一忌:不要在循環體中創建對象。這種做法會在內存中保存N份這個對象的引用會浪費大量的內存空間”

          這是誤解。完全沒有在內存中保存N份對象的引用,循環體內聲明的對象也只是在java棧中占據一個位置。

          反而在循環體內聲明的對象因為其作用域只是在循環體內,更節約內存(雖然微乎其微)。
          ??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題)[未登錄] 2007-02-11 17:06 dreamstone

          to:sinoly
          我在平時寫代碼的時候也不是在循環體內創建,但是記憶中差別不大。
          對于你的說法,
          1,String看不出效果,自定義對象和圖形對象能有效果?
          在印象中無論是什么,這里保留的都是一個引用,應該是一樣大的。所以應該沒有對象和圖形的差別。而且第一種做法不會產生垃圾對象,只會出現大量的引用。一個引用占用的內存是很小的,不會是大量的。但是如果循環次數很多,也是可觀的,所以我平時也是寫在循環體之外。
          2,如果寫在外邊,其實并不一定就快,因為在里邊寫的話過了循環體就過了它的有效范圍,可以被回收了,雖然并不一定立即回收,但如果第二種寫法對象則不能回收。恰恰相反,如果對象很大,例如圖形控件,活著保存大量數據的Bean,這個時候這個對象要到函數結束才會被回收,如果函數體很長,活著函數的執行時間很長,那么這個才是更消耗內存的。所以說哪種寫法要看情況而定。
          3,如何判定一個程序的好壞?這個是個綜合問題,要考慮很多因素,但是在印象種無論如何方法二應該是比方法一快的,結果剛好相反,開啟這個帖子主要是為了這個問題。就是為什么會這樣?而不是討論哪個方法更好。
          ??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 17:17 daydream

          java字節碼中,對于每一個方法都有一個max_locals屬性,指出方法的局部變量所需要的存儲空間(以字為單位)。

          對于一樓的例子,如果把
          String str = //
          移到循環體外,則max_locals會比在循環體內更大。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題)[未登錄] 2007-02-11 17:35 dreamstone

          to:sinoly
          看了你寫的關于對象創建的問題,我們說的不是一個問題啊。
          你的問題是:在循環體種創建相同的對象,就是作用一樣的對象,這個當然是浪費內存了。這種問題不需要再討論。
          我的問題是這樣的,并不再創建對象上,例如,如下問題,從List種取出對象,可以有兩種寫法,
          List<Object> list= //...一個已經存在的List
          方法一
          for(int i=0;i<list.size();i++){
          Object obj = list.get(i);
          }
          方法二
          Object obj= null;
          for(int i=0;i<list.size();i++){
          obj= list.get(i);
          }
          這個里邊根本沒有創建對象的問題,有的問題是方法一會多很多引用,方法二會讓一個引用保存期很長,同時對象有效期也變的很長。(其實你的性能優化的文章種應該指出這個問題的。)

          另外提示一下,對于我第一個例子中寫的:
          for (int i=0;i<n;i++){
          String str = "";
          }
          在這個函數中只會創建一個對象,因為String是非可變對象,虛擬機會自動重用,這個你可以參照一下JLS中的解釋。只有這種情況才是浪費
          for (int i=0;i<n;i++){
          String str = new String("");
          }
          最后感謝你參與,另外提一點建議:
          1,回文或者寫文章前應該先確認一下自己的觀點是否是對的,最好給出證明,雖然確認了也不能保證一定是對的,但是至少做過了,這是一種態度。我也是一直這么要求自己,無論多么簡單的問題,都給出一個思考的過程,因為這樣對看文章的人有幫助。例如你上邊說到的,如果是自定義對象活著圖形對象的觀點,剛好是錯誤的證明,你可以這樣試試。虛擬機內存設置64M,如果你在List中取出一個60M的對象,然后在循環之后再new一個10M的對象,方法一是可以運行的,雖然說慢,但方法二就OutOfMemory了。除非你在循環之后設置
          變量= null,但這種做法是否更好只得商榷.

          最后說明一點,應用開發和底層框架開發其實有很多東西是不同的。如果實際負責過項目就了解的。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 17:41 dreamstone

          @daydream
          謝謝再次回復,想問一下關于max_locals這個屬性,
          1,為什么放到循環體外反而會更大呢?能給簡單講一下為什么嗎?活著給一個能查到原因的方向。
          2,另外這個max_locals變大后為什么會影響性能呢?在什么時候會使用到max_locals這個屬性呢?
          謝謝
          ??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 18:40 daydream

          對下面2個方法,foo1需要的max_locals是4,foo2是5,

          foo1需要的4大概是:this變量占1個字、方法參數x占1個字、
          第一段循環的時候,變量i占一個字、s1占一個字,第二個循環的時候,i、s1已經超出作用域,所以,變量j、s2占用了和i、s1重疊的空間,所以最多需要4個字就夠了。

          foo2方法中s1的作用域直到方法結束,所以需要5個字長度。

          max_locals變大后應該不會影響到性能,但是我這兒的意思是說,將局部變量放在循環體內聲明并不會導致性能下降。

          void foo1(int x) {
          for (int i = 0; i < 1000; i++) {
          String s1 = "...";
          }
          for (int j = 0; j < 1000; j++) {
          String s2 = "....";
          }
          }

          void foo2(int x) {
          String s1 = "...";
          for (int i = 0; i < 1000; i++) {
          // other code.....
          }
          for (int j = 0; j < 1000; j++) {
          String s2 = "....";
          }
          }
          ??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 18:44 daydream

          另外,將局部變量放在循環體內聲明,也不會導致多出來很多引用。
          因為,局部變量對應于java棧的偏移是在編譯時就確定的,并不是在運行期動態分配的。循環體內的局部變量對應的是同一個偏移位置。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 18:48 daydream

          另外,max_locals只是編譯器在編譯時確定,存放在字節碼中,供JVM在運行期調用方法時分配java棧幀大小用的,對于程序員應該沒什么用,因為程序也沒辦法訪問java棧。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 19:07 dreamstone

          現在大概明白了max_locals的作用,但是我這測試出的問題還在。不知道為什么第一個方法要比第二個方法快。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 19:10 dreamstone

          另外我看你上邊寫的用我的代碼,測試結果是接近的,可我怎么測都是差距一秒啊,你的運行環境 ?
          我是xp下,試過eclipse運行,試過直接控制臺用java命令運行。
          jdk1.5??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) 2007-02-11 19:37 daydream

          運行你的第二段測試代碼:
          先運行test1共5次,結果:3406、3453、3391、3453、3391
          然后把代碼改成test2,運行5次,結果:
          3406、3406、3406、3437、3391

          運行環境:XP、512M內存、Eclipse3.2下,JDK6.0,沒有加啟動參數,默認最大內存好像是64M。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題)[未登錄] 2007-02-12 06:00 dreamstone

          郁悶了,我的兩臺電腦,測試的結果都是穩定的差不到1秒
          xp sp2 eclipse3.2.1 jdk1.5 啟動參數也沒加,最大內存開始是512,后來改成256和64都試了。
          結果test1:2938 2953 2954 2954 2969
          test2:3797 3797 3781 4000 3797 3797
          我今天再找別人試一下,看看什么情況。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題)[未登錄] 2007-02-12 09:56 dreamstone

          又找了幾個同事幫忙測試了一下,果然,有的差距是100毫秒,有的200,有的基本沒差距。我公司的電腦差距是200,也就是說我家里的測試不準確,真實不可思議,我在家測了很多次,都穩定在差距1000毫秒,呵呵。不過問題總算解決了。
          另外經過測試發現在這個問題上,amd的cpu比intel的快,單核的比雙核的快。。有意思。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) (問題結束)[未登錄] 2007-02-12 18:13 JIm

          注意jdk版本,一些基本的東西隨著JDK不斷的更新,都會有改變。對JAVA沒什么好印象!??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) (問題結束) 2007-02-12 19:15 dreamstone

          有些是跟版本有關的,這個跟版本沒關系,呵呵。
          為什么對java印象不好呢,每個語言都有它的好處和壞處。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) (問題結束) 2007-02-12 20:10 dreamstone

          剛才又做了個測試,一個很有意思的結果:使用ibm的ibm_sdk50測試結果剛好相反,執行10億次,方法二比方法一快了200毫秒。挺有意思。

          不過結論是一樣的,就是兩種方法性能差別很小,但方法二讓對象的有效期變長了,如果是大對象(例如圖形對象,數據bean對象)則不好,所以一般情況下應選擇方法一的寫法。或者方法二的寫法加上手動清空釋放對象。??回復??更多評論??

          #?re: 開啟一個新的問題(關于聲明變量的性能問題) (問題結束)2007-02-13 09:02 Welkin Hu

          牛啊!
          俺可很少關注到這一層。俺通常來說會用第一種的。可以省下俺4300ms寫代碼的時間。??回復??更多評論??

          posted on 2007-02-26 09:21 liaojiyong 閱讀(537) 評論(0)  編輯  收藏 所屬分類: Java

          主站蜘蛛池模板: 黔江区| 泌阳县| 驻马店市| 曲麻莱县| 广宗县| 谷城县| 巴林左旗| 通海县| 屏东县| 沐川县| 永平县| 桃园县| 行唐县| 南平市| 溧阳市| 二连浩特市| 西乡县| 太原市| 台北市| 玉树县| 田东县| 霞浦县| 土默特左旗| 铜山县| 桐梓县| 莱西市| 寿阳县| 射洪县| 集安市| 荣昌县| 吴江市| 邵阳县| 新河县| 泗阳县| 榆林市| 昌图县| 内乡县| 德钦县| 若尔盖县| 荥经县| 大新县|