2013-10-04 27 views
2

我想跟蹤在Java應用程序中調用的groovy eval(字符串)方法的性能問題的來源。如果我執行下面的代碼;Groovy腳本eval(字符串)方法的性能問題

String pattern = "test = ['one','two','three']"; 
engine.eval(pattern) 

它運行在幾乎沒有時間(0至1毫秒)

但是,如果我說了以下

String first = "['one','two','three']"; 
String pattern = "test = " + first; // "identical" String to first approach 
engine.eval(pattern) 

花費〜30 +毫秒運行。

更糟糕的是,經過幾千次調用後,它將高達60-70ms,儘管我對這一點的關注遠低於兩種實現之間的時間增量。

任何解釋爲什麼發生這種情況/有關如何避免這種情況的建議?我懷疑它與Java和/或Groovy編譯器有關,我已經開始研究compile()方法,但是如果有一種簡單的方法來使現有的代碼可以工作(更少的事情需要改變辦法)。

+0

沒錯,但字符串是通過他們傳遞到EVAL時間(相當於),這就是爲什麼我懷疑這是一個編譯器或運行時優化的問題(道歉,如果這就是你說的話)... – user1251193

+1

你如何構建引擎?你在使用JSR-223嗎?如果是這樣,看看http://groovy.codehaus.org/JSR+223+Scripting+with+Groovy和最後一段:'引擎保持默認硬引用腳本函數.'如果你使用一個常量內嵌字符串然後groovy可能在這裏優化。而串聯會導致表達式被視爲新的字符串,而不是可能導致這種差異的緩存腳本。 –

回答

0

eval在任何語言中都是出了名的緩慢,並經常建議遠離(「eval是邪惡的」)。然而,在你的代碼中,似乎更多的是字符串連接導致了減速,而不是eval本身。

不過,幾毫秒不應該引起關注 - 您是否確定這會導致代碼中的性能瓶頸?對代碼進行微觀管理可能會在未來導致錯誤和不明確的代碼,這可能會造成更糟糕的影響。

+0

不幸的是,這是可能被稱爲幾千次,從幾秒鐘到5分鐘以上的工作。更糟的是,由於我提到的eval運行時間增加,第二次運行相同的作業需要10分鐘(下一個15-20分鐘)。如果我們解決了第二個問題,我們可以接受5分鐘的處理,但不像編譯腳本那麼簡單,因爲它不能保證在每個調用中都是相同的。我們在整個應用程序中使用這種方法,並且性能不是問題 - 只有這種情況下,我們需要爲表達式加前綴...... Thx – user1251193

+0

@ user1251193嗯,您可以嘗試使用StringBuilder嗎?這可能會節省一點時間,具體取決於您正在進行的連接數量。 – Igor

+0

這超出了我的想法,但它只是一個連接,延遲似乎發生在評估內而不是之前。我認爲這更多的是導致延遲的靜態與動態上下文,但如果它有所作爲,我會試一試併發布更新。再次感謝... – user1251193

0

你是如何測量調用時間的?如果可能的話,我會測量每個部分(String building和eval()),以確定哪個部分花費時間,哪些部分隨迭代次數增加。既然你說時間隨着迭代次數的增加而增加,看看垃圾收集。第一種情況使用單個字符串 - 在後面的情況下,每次迭代都會創建一個新的字符串,因此會消耗內存。您可能會遇到堆限制。

查看此視圖​​的一個非常有用的工具是VisualVM。 http://visualvm.java.net/

+0

謝謝保羅。時機僅適用於eval()。這個問題始於PermGen填滿的問題,所以我現在已經積極參與探查器幾天(我使用YourKit)。有一次,我確實懷疑GC,但是由於卸載了類並且清除了PermGen(這是非常緊急的和非常密集的),而不是堆(我有GB的空閒)。我想出了另一種方法,它的運行時間約爲1ms(我會在稍後時間發佈),但我仍然希望有人能夠解釋這種行爲的潛在原因,這對我來說仍然是一個謎。 – user1251193

+0

@ user1251193你可以分享你的完整基準嗎?根據固定字符串的使用方式,固定字符串可能會被攔截,也許Groovy eval有一個優化,只編譯一次腳本,然後使用緩存實例進行第二次迭代。拼接強制創建一個新的字符串。而且Groovy和Java版本也會很有趣。 –

+0

我不是一個分享實際代碼的自由,但我已經或多或少地重現了上面顯示的問題 - 通過在調用eval之前和之後寫入日誌來測量時間(不確定是什麼,如果有的話,可能會有影響)。時序差異發生在非常調用時(<1ms,沒有級聯vs 30 + ms),並且在後續調用之後,任何一種方法的時序都不會改善,即使引擎本身通過多次迭代被緩存(在這種情況下,正在執行一個完全相同的字符串)。 – user1251193

0

最終,我們認識到,

eval("['one','two','three']") 

回報正是這一點,所以

Object test = engine.eval(first) 

相當於

engine.eval("test = " + "['one','two','three']") // see text above for exact syntax 
Object test = engine.get("test") 

,但它在所有運行有效的沒有時間。我仍然非常好奇爲什麼動態模式會對eval()的性能產生如此巨大的影響,但是我懷疑需要有人深入理解Java和/或groovy運行時元素它上面有任何光。

感謝大家試圖協助。 Regards,

ps。

2

如果你看https://github.com/groovy/groovy-core/blob/master/subprojects/groovy-jsr223/src/main/java/org/codehaus/groovy/jsr223/GroovyScriptEngineImpl.java?source=cc你會發現一個緩存,用於將字符串映射到類。問題是ManagedConcurrentMap基本上是一個身份hashmap,這意味着如果你一遍又一遍地使用相同的字符串,它會很快,因爲在隨後的運行中跳過編譯。問題版本每次都會創建一個新的字符串,因此需要每次編譯一次,導致每次都會生成一個新的類。

如何避免這樣的:創建一個基於散列緩存

+0

非常感謝blackdrag!你解決了這個謎,很可能有幾個。既然你是一個看起來很熟悉Groovy內部構件的人(我已閱讀了你的許多文章),你覺得他們使用標識映射而不是標準映射看起來很奇怪嗎?剛剛瞭解了這一點,我們正忙於在整個應用程序中查看我們對Groovy的使用情況 - 由於有效字符串本質上是相同的,因此我們預計會有一些地方需要緩存代碼。難怪我們產生了這麼多的課程/使用這麼多的permgen。再次感謝! – user1251193

+0

如果你有時間,你的另一個問題。鑑於這一消息,我們正在考慮保留我們自己的查找地圖,以確保我們始終返回相同的字符串 - 任何潛在的問題/疑慮?如果不是的話,我會發佈一個更新,告訴我們如何爲我們... – user1251193

+0

對於遲到的答案感到抱歉...只要您保持併發和記憶,我發現自定義查找映射沒有問題。但這兩個並不是微不足道的。 至於爲什麼一個IdentityMap ...坦率地說...我做了這個改變,我想我可能忽略了那部分...對我感到羞恥 – blackdrag