Java十大简单性能优化
關(guān)于“ web scale ”這個(gè)流行詞有很多炒作,人們花了很多時(shí)間來重新組織他們的應(yīng)用程序體系結(jié)構(gòu),以使其系統(tǒng)“規(guī)模化”。
但是什么是擴(kuò)展,我們?nèi)绾未_保可以擴(kuò)展?
縮放的不同方面
上面提到的炒作主要是關(guān)于擴(kuò)展負(fù)載 ,即確保一個(gè)適用于1個(gè)用戶的系統(tǒng)也適用于10個(gè)用戶,100個(gè)用戶或數(shù)百萬個(gè)用戶。 理想情況下,您的系統(tǒng)應(yīng)盡可能“無狀態(tài)”,以便可以在網(wǎng)絡(luò)中的任何處理單元上轉(zhuǎn)移和轉(zhuǎn)換真正保留的少量狀態(tài)。 當(dāng)負(fù)載是您的問題時(shí),延遲可能就沒有了,因此,如果單個(gè)請求花費(fèi)50-100毫秒就可以了。 這通常也稱為橫向擴(kuò)展
擴(kuò)展的一個(gè)完全不同的方面是擴(kuò)展性能 ,即,確保適用于1條信息的算法也適用于10條或100條或數(shù)百萬條。 Big O Notation最好地描述了這種縮放是否可行。 延遲是擴(kuò)展性能的殺手。 您想盡一切可能將所有計(jì)算保持在一臺計(jì)算機(jī)上。 這通常也稱為放大
如果有免費(fèi)午餐之類的東西( 沒有 ),我們可以無限期地結(jié)合擴(kuò)大規(guī)模和擴(kuò)大規(guī)模。 無論如何,今天,我們將研究一些非常簡單的方法來改善性能。
大O符號
Java 7的ForkJoinPool以及Java 8的并行Stream有助于并行化內(nèi)容,這在將Java程序部署到多核處理器計(jì)算機(jī)上時(shí)非常有用。 與在網(wǎng)絡(luò)上的不同計(jì)算機(jī)上進(jìn)行擴(kuò)展相比,這種并行性的優(yōu)勢在于您幾乎可以完全消除延遲影響,因?yàn)樗袃?nèi)核都可以訪問同一內(nèi)存。
但是,不要被并行性的效果所迷惑! 請記住以下兩件事:
- 并行主義吞噬了您的核心。 這對于批處理非常有用,但是對于異步服務(wù)器(例如HTTP)來說卻是一場噩夢。 在過去的幾十年中,我們使用單線程servlet模型是有充分的理由的。 因此,并行性僅在擴(kuò)大規(guī)模時(shí)有用。
- 并行性對算法的Big O表示法沒有影響。 如果您的算法是O(n log n) ,并且讓該算法在c核上運(yùn)行,那么您仍然會(huì)擁有O(n log n / c)算法,因?yàn)閏在算法復(fù)雜度上是微不足道的常數(shù)。 您將節(jié)省掛鐘時(shí)間,但不會(huì)降低復(fù)雜性!
當(dāng)然,提高性能的最好方法是降低算法復(fù)雜度。 當(dāng)然,殺手是實(shí)現(xiàn)O(1)或準(zhǔn)O(1) ,例如HashMap查找。 但這并不總是可能的,更不用說輕松了。
如果您不能降低復(fù)雜性,只要找到合適的位置,只要對算法真正重要的地方進(jìn)行調(diào)整,您仍然可以獲得很多性能。 假定算法的以下直觀表示形式:
如果我們要處理單個(gè)數(shù)量級,該算法的總體復(fù)雜度為O(N 3 )或O(N x O x P) 。 但是,在分析此代碼時(shí),您可能會(huì)發(fā)現(xiàn)一個(gè)有趣的場景:
- 在開發(fā)框中,左分支( N -> M -> Heavy operation )是您可以在探查器中看到的唯一分支,因?yàn)镺和P的值在開發(fā)樣本數(shù)據(jù)中很小。
- 但是,在生產(chǎn)中,右分支( N -> O -> P -> Easy operation或NOPE )確實(shí)會(huì)造成麻煩。 您的運(yùn)營團(tuán)隊(duì)可能已經(jīng)使用AppDynamics或DynaTrace或某些類似的軟件解決了這一問題。
沒有生產(chǎn)數(shù)據(jù),您可能會(huì)Swift得出結(jié)論并優(yōu)化“繁重的操作”。 您將其運(yùn)送到生產(chǎn)環(huán)境,并且修復(fù)無效。
除了以下事實(shí)外,沒有最佳的黃金法則:
- 設(shè)計(jì)良好的應(yīng)用程序更容易優(yōu)化
- 過早的優(yōu)化不會(huì)解決任何性能問題,但是會(huì)使您的應(yīng)用程序設(shè)計(jì)欠佳,從而使優(yōu)化變得更加困難
足夠的理論。 假設(shè)您已找到問題所在的正確分支。 很容易在生產(chǎn)中吹起一個(gè)非常簡單的操作,因?yàn)樗环Q為很多次(如果N , O和P大)。 在不可避免的O(N 3 )算法的葉子節(jié)點(diǎn)存在問題的情況下,請閱讀本文。 這些優(yōu)化不會(huì)幫助您擴(kuò)展規(guī)模。 他們將幫助您暫時(shí)節(jié)省客戶的時(shí)間,將整個(gè)算法的困難改進(jìn)推遲到以后!
以下是Java中最容易進(jìn)行的10個(gè)性能優(yōu)化:
1.使用StringBuilder
這幾乎是所有Java代碼中的默認(rèn)設(shè)置。 盡量避免使用+運(yùn)算符。 當(dāng)然,您可能會(huì)爭辯說它仍然只是StringBuilder語法糖,例如:
String x = "a" + args.length + "b";…編譯成
0 new java.lang.StringBuilder [16]3 dup4 ldc <String "a"> [18]6 invokespecial java.lang.StringBuilder(java.lang.String) [20]9 aload_0 [args] 10 arraylength 11 invokevirtual java.lang.StringBuilder.append(int) : java.lang.StringBuilder [23] 14 ldc <String "b"> [27] 16 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [29] 19 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [32] 22 astore_1 [x]但是會(huì)發(fā)生什么,如果以后需要用可選部分修改String呢?
String x = "a" + args.length + "b";if (args.length == 1)x = x + args[0];現(xiàn)在,您將擁有第二個(gè)StringBuilder ,它只是不必要地消耗了堆內(nèi)存,從而給GC帶來了壓力。 改寫這個(gè):
StringBuilder x = new StringBuilder("a"); x.append(args.length); x.append("b");if (args.length == 1);x.append(args[0]);帶走
在上面的示例中,如果您使用顯式StringBuilder實(shí)例,或者依賴Java編譯器為您創(chuàng)建隱式實(shí)例,則可能完全不相關(guān)。 但是請記住,我們在NOPE分支中 。 每個(gè)CPU周期我們都在浪費(fèi)像GC這樣愚蠢的東西或分配StringBuilder的默認(rèn)容量,我們浪費(fèi)的時(shí)間是N x O x P次。
根據(jù)經(jīng)驗(yàn),請始終使用StringBuilder而不是+運(yùn)算符。 如果可以的話,如果您的String構(gòu)建比較復(fù)雜,則可以在多個(gè)方法中保留StringBuilder引用。 這是jOOQ在生成復(fù)雜的SQL語句時(shí)所做的。 只有一個(gè)StringBuilder可以“遍歷”您的整個(gè)SQL AST(抽象語法樹)
為了大聲喊叫,如果仍然有StringBuffer引用,請用StringBuilder替換它們。 您實(shí)際上幾乎不需要同步正在創(chuàng)建的字符串。
2.避免使用正則表達(dá)式
正則表達(dá)式相對便宜且方便。 但是,如果您位于NOPE分支中 ,那么它們將是您最糟糕的事情。 如果您絕對必須在計(jì)算密集型代碼節(jié)中使用正則表達(dá)式,則至少要緩存Pattern引用,而不要一直重新編譯它:
static final Pattern HEAVY_REGEX = Pattern.compile("(((X)*Y)*Z)*");但是如果你的正則表達(dá)式真的很傻
String[] parts = ipAddress.split("\\.");……那么您真的最好使用普通的char[]或基于索引的操作。 例如,這個(gè)完全不可讀的循環(huán)執(zhí)行相同的操作:
int length = ipAddress.length(); int offset = 0; int part = 0; for (int i = 0; i < length; i++) {if (i == length - 1 || ipAddress.charAt(i + 1) == '.') {parts[part] = ipAddress.substring(offset, i + 1);part++;offset = i + 2;} }……這也說明了為什么您不應(yīng)該進(jìn)行任何過早的優(yōu)化。 與split()版本相比,這是無法維護(hù)的。
挑戰(zhàn):讀者中的聰明人可能會(huì)找到更快的算法。
帶走
正則表達(dá)式很有用,但要付出一定的代價(jià)。 如果您深入了解NOPE分支 ,則必須不惜一切代價(jià)避免使用正則表達(dá)式。 提防各種使用正則表達(dá)式的JDK String方法,例如String.replaceAll()或String.split() 。
請改用諸如Apache Commons Lang之類的流行庫來進(jìn)行String操作。
3.不要使用iterator()
現(xiàn)在,此建議實(shí)際上并不適用于一般用例,而僅適用于NOPE分支的深處。 但是,您應(yīng)該考慮一下。 編寫Java-5樣式的foreach循環(huán)很方便。 您可以完全忘記循環(huán)內(nèi)部,然后編寫:
for (String value : strings) {// Do something useful here }但是,每次遇到此循環(huán)時(shí),如果strings是Iterable ,則將創(chuàng)建一個(gè)新的Iterator實(shí)例。 如果您使用ArrayList ,這將在堆上分配一個(gè)3 ints的對象:
private class Itr implements Iterator<E> {int cursor;int lastRet = -1;int expectedModCount = modCount;// ...相反,您可以編寫以下等效循環(huán),并僅在堆棧上“浪費(fèi)”一個(gè)int值,這非常便宜:
int size = strings.size(); for (int i = 0; i < size; i++) {String value : strings.get(i);// Do something useful here }…或者,如果您的列表沒有真正改變,您甚至可以對其數(shù)組版本進(jìn)行操作:
for (String value : stringArray) {// Do something useful here }帶走
從可寫性和可讀性以及從API設(shè)計(jì)的角度來看,迭代器,Iterable和foreach循環(huán)都非常有用。 但是,它們?yōu)槊看蔚诙焉蟿?chuàng)建一個(gè)小的新實(shí)例。 如果您多次運(yùn)行此迭代,則要確保避免創(chuàng)建此無用的實(shí)例,而改為編寫基于索引的迭代。
討論區(qū)
關(guān)于上述部分的一些有趣的分歧(特別是用索引訪問代替Iterator用法) 已經(jīng)在Reddit上進(jìn)行了討論 。
4.不要調(diào)用該方法
有些方法簡單昂貴。 在我們的NOPE分支示例中,葉子上沒有這樣的方法,但是您很可能有一個(gè)。 假設(shè)您的JDBC驅(qū)動(dòng)程序需要經(jīng)歷難以置信的麻煩才能計(jì)算ResultSet.wasNull()的值。 您自己的SQL框架代碼可能如下所示:
if (type == Integer.class) {result = (T) wasNull(rs, Integer.valueOf(rs.getInt(index))); }// And then... static final <T> T wasNull(ResultSet rs, T value) throws SQLException {return rs.wasNull() ? null : value; }每次您從結(jié)果集中獲取一個(gè)int ,此邏輯將立即調(diào)用ResultSet.wasNull() 。 但是getInt()合同顯示為:
返回:列值; 如果值為SQL NULL,則返回值為0
因此,對上述內(nèi)容進(jìn)行簡單但可能很大的改進(jìn)將是:
static final <T extends Number> T wasNull(ResultSet rs, T value ) throws SQLException {return (value == null || (value.intValue() == 0 && rs.wasNull())) ? null : value; }因此,這很容易:
帶走
不要在算法的“葉子節(jié)點(diǎn)”中調(diào)用昂貴的方法,而要緩存調(diào)用,或者在方法合同允許的情況下避免調(diào)用。
5.使用原語和堆棧
上面的示例來自jOOQ ,它使用了很多泛型,因此被迫對byte , short , int和long使用包裝器類型-至少在泛型將在Java 10和項(xiàng)目Valhalla中實(shí)現(xiàn)特殊化之前。 但是您的代碼中可能沒有此約束,因此應(yīng)采取所有措施替換:
// Goes to the heap Integer i = 817598;… 這樣:
// Stays on the stack int i = 817598;使用數(shù)組時(shí),情況變得更糟:
// Three heap objects! Integer[] i = { 1337, 424242 };… 這樣:
// One heap object. int[] i = { 1337, 424242 };帶走
當(dāng)您深入了解NOPE分支時(shí) ,應(yīng)該非常警惕使用包裝器類型。 可能會(huì)給GC帶來很大壓力,必須時(shí)刻加油清理GC。
一種特別有用的優(yōu)化可能是使用某種原始類型并為其創(chuàng)建大型的一維數(shù)組,以及幾個(gè)定界符變量以指示編碼對象在數(shù)組上的確切位置。
trove4j是一個(gè)出色的原始集合庫,它比您的平均int[]要復(fù)雜一些 ,它隨LGPL一起提供。
例外
此規(guī)則有一個(gè)例外: boolean和byte值很少,因此無法完全由JDK緩存。 你可以寫:
Boolean a1 = true; // ... syntax sugar for: Boolean a2 = Boolean.valueOf(true);Byte b1 = (byte) 123; // ... syntax sugar for: Byte b2 = Byte.valueOf((byte) 123);對于其他整數(shù)基元類型的低值(包括char , short , int , long 。
但是僅當(dāng)您將它們自動(dòng)裝箱或調(diào)用TheType.valueOf() ,才調(diào)用構(gòu)造函數(shù)!
除非確實(shí)需要新實(shí)例,否則切勿在包裝器類型上調(diào)用構(gòu)造函數(shù)。
這個(gè)事實(shí)還可以幫助您為同事寫一個(gè)復(fù)雜的,愚蠢的愚人節(jié)玩笑
堆外
當(dāng)然,您可能還想嘗試堆外庫,盡管它們更多是一個(gè)戰(zhàn)略決策,而不是本地優(yōu)化。
彼得·勞瑞(Peter Lawrey)和本·科頓(Ben Cotton)撰寫的有關(guān)該主題的有趣文章是: OpenJDK和HashMap…安全地教老狗新手(超堆!)技巧
6.避免遞歸
像Scala這樣的現(xiàn)代函數(shù)式編程語言鼓勵(lì)使用遞歸,因?yàn)樗鼈兲峁┝藢⑽策f歸算法優(yōu)化回迭代算法的方法 。 如果您的語言支持這種優(yōu)化,則可能會(huì)很好。 但是即使如此,算法的最細(xì)微改動(dòng)也可能會(huì)產(chǎn)生一個(gè)分支,從而阻止您的遞歸成為尾遞歸。 希望編譯器能夠檢測到這一點(diǎn)! 否則,您可能會(huì)浪費(fèi)大量的堆棧框架,而這些內(nèi)容可能只使用了幾個(gè)局部變量就已經(jīng)實(shí)現(xiàn)了。
帶走
除了以下內(nèi)容外,沒有什么要說的:當(dāng)您深入NOPE分支時(shí),始終喜歡迭代而不是遞歸。
7.使用entrySet()
當(dāng)您要遍歷Map ,同時(shí)需要鍵和值時(shí),必須有充分的理由編寫以下內(nèi)容:
for (K key : map.keySet()) {V value : map.get(key); }…而不是以下內(nèi)容:
for (Entry<K, V> entry : map.entrySet()) {K key = entry.getKey();V value = entry.getValue(); }當(dāng)您在NOPE分支中時(shí) ,無論如何,您都應(yīng)該警惕地圖,因?yàn)楹芏郞(1)地圖訪問操作仍然很多。 而且訪問也不是免費(fèi)的。 但是至少,如果您不能沒有地圖,請使用entrySet()對其進(jìn)行迭代! 無論如何,都有Map.Entry實(shí)例,您只需要訪問它即可。
帶走
在地圖迭代期間同時(shí)需要鍵和值時(shí),請始終使用entrySet() 。
8.使用EnumSet或EnumMap
在某些情況下,映射中可能的鍵數(shù)是預(yù)先已知的,例如在使用配置映射時(shí)。 如果該數(shù)字相對較小,則應(yīng)真正考慮使用EnumSet或EnumMap ,而不是常規(guī)的HashSet或HashMap 。 通過查看EnumMap.put()可以很容易地解釋這一點(diǎn):
private transient Object[] vals;public V put(K key, V value) {// ...int index = key.ordinal();vals[index] = maskNull(value);// ... }此實(shí)現(xiàn)的本質(zhì)是這樣一個(gè)事實(shí),即我們擁有一個(gè)索引值數(shù)組,而不是哈希表。 當(dāng)插入一個(gè)新值時(shí),我們要查找映射項(xiàng)的所有工作就是詢問枚舉的常量序數(shù),該序數(shù)由Java編譯器在每種枚舉類型上生成。 如果這是一個(gè)全局配置映射(即僅一個(gè)實(shí)例),則提高的訪問速度將幫助EnumMap大大優(yōu)于HashMap ,后者可能使用較少的堆內(nèi)存,但必須在每個(gè)鍵上運(yùn)行hashCode()和equals() 。
帶走
Enum和EnumMap是非常好的朋友。 每當(dāng)您使用類似枚舉的結(jié)構(gòu)作為鍵時(shí),請考慮實(shí)際上使這些結(jié)構(gòu)成為枚舉并將其用作EnumMap鍵。
9.優(yōu)化您的hashCode()和equals()方法
如果您不能使用EnumMap ,至少要優(yōu)化您的hashCode()和equals()方法。 一個(gè)好的hashCode()方法至關(guān)重要,因?yàn)樗鼘⒆柚箤Ω嘿F的equals()進(jìn)一步調(diào)用,因?yàn)樗鼘槊總€(gè)實(shí)例集生成更多不同的哈希存儲桶。
在每個(gè)類層次結(jié)構(gòu)中,您可能都有流行和簡單的對象。 讓我們看一下jOOQ的org.jooq.Table實(shí)現(xiàn)。
hashCode()的最簡單,最快的實(shí)現(xiàn)方法是:
// AbstractTable, a common Table base implementation:@Override public int hashCode() {// [#1938] This is a much more efficient hashCode()// implementation compared to that of standard// QueryPartsreturn name.hashCode(); }…其中name只是表名。 我們甚至不考慮表的模式或任何其他屬性,因?yàn)楸砻ǔT跀?shù)據(jù)庫中足夠不同。 另外,該name是一個(gè)字符串,因此它內(nèi)部已經(jīng)有一個(gè)緩存的hashCode()值。
該注釋很重要,因?yàn)锳bstractTable擴(kuò)展了AbstractQueryPart ,它是任何AST(抽象語法樹)元素的通用基本實(shí)現(xiàn)。 通用AST元素沒有任何屬性,因此它不能做任何假設(shè)來優(yōu)化hashCode()實(shí)現(xiàn)。 因此,重寫的方法如下所示:
// AbstractQueryPart, a common AST element // base implementation:@Override public int hashCode() {// This is a working default implementation. // It should be overridden by concrete subclasses,// to improve performancereturn create().renderInlined(this).hashCode(); }換句話說,必須觸發(fā)整個(gè)SQL呈現(xiàn)工作流以計(jì)算公共AST元素的哈希碼。
equals()事情變得更加有趣
// AbstractTable, a common Table base implementation:@Override public boolean equals(Object that) {if (this == that) {return true;}// [#2144] Non-equality can be decided early, // without executing the rather expensive// implementation of AbstractQueryPart.equals()if (that instanceof AbstractTable) {if (StringUtils.equals(name, (((AbstractTable<?>) that).name))) {return super.equals(that);}return false;}return false; }第一件事: 總是 (不僅在NOPE分支中 )提前中止每個(gè)equals()方法,如果:
- this == argument
- this "incompatible type" argument
請注意,如果您使用instanceof檢查兼容類型,則后一個(gè)條件包括argument == null 。 之前,我們在“編碼Java的10個(gè)微妙的最佳實(shí)踐”中已經(jīng)對此進(jìn)行了博客撰寫。
現(xiàn)在,在明顯的情況下盡早中止比較之后,您可能還想在可以做出部分決策時(shí)就中止比較。 例如,jOOQ的Table.equals()的Table.equals()是要使兩個(gè)表相等,無論具體的實(shí)現(xiàn)類型如何,它們都必須具有相同的名稱。 例如,這兩個(gè)項(xiàng)目不可能相等:
- com.example.generated.Tables.MY_TABLE
- DSL.tableByName("MY_OTHER_TABLE")
如果argument 不能等于this ,并且如果我們可以輕松地進(jìn)行檢查,那么我們可以進(jìn)行檢查,如果檢查失敗,則中止。 如果檢查成功,我們?nèi)匀豢梢詮膕uper進(jìn)行更昂貴的實(shí)現(xiàn)。 鑒于Universe中的大多數(shù)對象不相等,我們將通過簡化此方法來節(jié)省大量CPU時(shí)間。
有些對象比其他對象更平等
對于jOOQ,大多數(shù)實(shí)例實(shí)際上是由jOOQ源代碼生成器生成的表,該表的equals()實(shí)現(xiàn)甚至得到了進(jìn)一步優(yōu)化。 其他數(shù)十種表類型(派生表,表值函數(shù),數(shù)組表,聯(lián)接表,數(shù)據(jù)透視表,公用表表達(dá)式等)可以保持其“簡單”實(shí)現(xiàn)。
10.集合思考,而不是個(gè)別思考
最后但并非最不重要的一點(diǎn)是,它與Java不相關(guān),但適用于任何語言。 此外,我們將離開NOPE分支,因?yàn)榇私ㄗh可能只是幫助您從O(N 3 )遷移到O(n log n)或類似的東西。
不幸的是,許多程序員認(rèn)為是簡單的本地算法。 他們一步一步地解決問題,逐分支,逐循環(huán),逐方法。 那就是命令式和/或函數(shù)式編程風(fēng)格。 從純命令式到面向?qū)ο?#xff08;仍然命令式)再到函數(shù)式編程時(shí),為“更大的畫面”建模變得越來越容易,但是所有這些樣式都缺少只有SQL,R和類似語言才能具備的功能:
聲明式編程。
在SQL中( 并且我們很喜歡,因?yàn)樗莏OOQ博客 ),您可以聲明要從數(shù)據(jù)庫中獲取的結(jié)果,而不會(huì)產(chǎn)生任何算法含義。 然后,數(shù)據(jù)庫可以考慮所有可用的元數(shù)據(jù)( 例如約束,鍵,索引等 ),以找出可能的最佳算法。
從理論上講,從一開始,這就是SQL和關(guān)系演算背后的主要思想。 在實(shí)踐中,SQL供應(yīng)商僅在最近十年才實(shí)施了高效的CBO(基于成本的優(yōu)化工具) ,因此請與我們保持在一起,直到2010年SQL最終釋放出其全部潛力(大約是時(shí)間!)。
但是,您不必執(zhí)行SQL即可進(jìn)行集合思考。 集合/收藏/袋子/清單可提供所有語言和庫。 使用集合的主要優(yōu)點(diǎn)是您的算法將變得更加簡潔。 編寫起來非常容易:
SomeSet INTERSECT SomeOtherSet而不是:
// Pre-Java 8 Set result = new HashSet(); for (Object candidate : someSet)if (someOtherSet.contains(candidate))result.add(candidate);// Even Java 8 doesn't really help someSet.stream().filter(someOtherSet::contains).collect(Collectors.toSet());有人可能會(huì)認(rèn)為函數(shù)式編程和Java 8將幫助您編寫更簡單,更簡潔的算法。 不一定是真的。 您可以將命令性的Java-7循環(huán)轉(zhuǎn)換為功能性的Java-8 Stream集合,但是您仍在編寫完全相同的算法。 編寫類似SQL的表達(dá)式是不同的。 這個(gè)…
SomeSet INTERSECT SomeOtherSet…可以由實(shí)施引擎以1000種方式實(shí)施。 正如我們今天所了解的那樣,在運(yùn)行INTERSECT操作之前,將兩個(gè)集合自動(dòng)轉(zhuǎn)換為EnumSet也許是明智的。 也許我們可以并行化此INTERSECT而無需對Stream.parallel()進(jìn)行低級調(diào)用。
結(jié)論
在本文中,我們討論了在NOPE分支上完成的優(yōu)化,即深入到高復(fù)雜度算法中。 在我們的案例中,作為jOOQ開發(fā)人員,我們對優(yōu)化我們的SQL生成感興趣:
- 每個(gè)查詢僅在單個(gè)StringBuilder上生成
- 我們的模板引擎實(shí)際上是解析字符,而不是使用正則表達(dá)式
- 我們會(huì)盡可能使用數(shù)組,尤其是在偵聽器上進(jìn)行迭代時(shí)
- 我們避免了不必調(diào)用的JDBC方法
- 等等…
jOOQ位于“食物鏈的底部”,因?yàn)樗?#xff08;次)API,在調(diào)用離開JVM進(jìn)入DBMS之前,我們的客戶應(yīng)用程序正在調(diào)用它。 位于食物鏈的底部意味著在jOOQ中執(zhí)行的每一行代碼都可能被稱為N x O x P倍,因此我們必須熱切地進(jìn)行優(yōu)化。
您的業(yè)??務(wù)邏輯不在NOPE分支中 。 但是您自己的本地基礎(chǔ)結(jié)構(gòu)邏輯可能是(自定義SQL框架,自定義庫等),應(yīng)該根據(jù)我們今天所看到的規(guī)則進(jìn)行審查。 例如,使用Java Mission Control或任何其他探查器。
翻譯自: https://www.javacodegeeks.com/2015/02/top-10-easy-performance-optimisations-java.html
總結(jié)
以上是生活随笔為你收集整理的Java十大简单性能优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 鸿蒙对比安卓的优势(安卓的优势)
- 下一篇: 幂等与时间解耦之旅