當在家工作好一陣子,去公司才發現桌上擺了過勞評量表。
✓ 覺得「我快要撐不下去了。」
✓ 覺得心力交瘁
✓ 覺得有挫折
✓ 一想到上班,你就覺得沒力
✓ 上班的每一刻都很煎熬
☓ 不工作的時候,你有足夠的精力陪朋友或家人
等等,要是一直沒來的話 …
當在家工作好一陣子,去公司才發現桌上擺了過勞評量表。
✓ 覺得「我快要撐不下去了。」
✓ 覺得心力交瘁
✓ 覺得有挫折
✓ 一想到上班,你就覺得沒力
✓ 上班的每一刻都很煎熬
☓ 不工作的時候,你有足夠的精力陪朋友或家人
等等,要是一直沒來的話 …
|
|
每一次轉換,別浪費額外的空間 8192 的 StringBuilder
。如果真的需要這種 IO 的緩衝區,可以直接宣告在 BufferWriter
給定預設大小的緩衝區。
|
|
既然都知道了字串串接會有中間結果,我們可以直接分開寫 writer.write(token0);
和 writer.write(token1);
來達到相同效果。
|
|
這種可變參數的寫法會額外產生一個陣列,對於效能來說會很傷。
|
|
別中毒於 Java 或者是 OOP 的寫法。
|
|
在大多數的狀況下都可以直接存原本的物件。除非是在一些特定的序列化,或者是橫跨兩個不同 domain 的交互資料,這時候我們必須採用字串作為中介對象。
|
|
多餘的寫法。如果 wrapper class 有預設值,而且還不能被設置 null。那必然有些地方出錯。
|
|
這傢伙到底在宣告什麼?想用什麼?
Keywords: JNI, Java, ProcessBuilder, Thread Error
Error message: [os,thread] Failed to start thread - pthread_create failed (EINVAL) for attributes: stacksize: 136k, guardsize: 0k, detached.
Solution: -Djdk.lang.processReaperUseDefaultStackSize=true
From JNI process, it is fine to execute Java internal multi-threading, but can not use Runetime.exec or ProcessBuilder
to do anything. Even through adjust java stack-related option
|
|
cgroup (Linux control group) limition, and set_rlimit. Same error message will be displayed.
When you link large C/C++ program with large thread local storage (TLS), default minimum stacksize computation is not enough to create pthread in OS-level. Only to set -Djdk.lang.processReaperUseDefaultStackSize=true
to apply default pthread arguments. Then, it will be working for you.
無法透過 JNI 啟動的 JVM 運行外部程序,如 Runtime.exec
或者是 Process
都無法使用,卻可以使用內部執行緒,甚至達到了數百個執行緒。
首先,JVM 內部拆分了好幾種不同的執行緒使用策略,也有不同的資源需求判斷,去挖原始碼的時候會追到底層的 C 部分,就可以看出這些問題。但是錯誤訊息仍然不足,到了 JDK15 後,才能透過 -Xlog
dump 更多的配置訊息,但有些配置訊息的翻譯仍然有錯,還是得看原始碼去得知實際情況。
追了好幾天,調適了許多不同的 stack 配置都沒有起色。自己寫的小短碼 JNI 都沒有事情,架在產品上就突然運行失敗。因此,只能去找有沒有現有的產品開發或使用上發生類似的問題,不該去找任何的教學文件。最後挖到幾篇安裝不同版本的產品失敗,據說更改 JDK 版本就能解決,然後有人分析說因為 TLS 的大小變動才導致新版本突然無法工作,才明白果然是 JNI 上的問題。
實作 JNI 介面的時候,若連接的 C++ 程式中存在大量的 thread_local
變數時,那麼在每一個執行緒中都必須給予相對應的大小進行初始化。而對於 JVM 內部會透過一些預設 stack 配置進行最小化,透過 JNI 連接的時候,便會受到影響。
透過 -Djdk.lang.processReaperUseDefaultStackSize=true
取消最小化 stack 配置,直接使用預設的構造行為,這樣就能暫時解決 JNI 啟動的 JVM 無法運行外部程序的問題。很明顯地,每一條執行緒都會開相當大的 stack size,大部分的情況下預設最多為 4 MB,而實際工作的產品中預設就要 20 MB,這個得從其關聯函數庫去分析為什麼需要這麼大的堆疊,可能是寫錯,也或者是連結太多沒有必要的庫進來。
|
|
對於可以直接計算得到的,應該直接展開。
|
|
數學很重要,可以減少很多分支判斷。
|
|
|
|
命名盡量貼切型態,不然有時候真的會有很奇怪。
|
|
在建構子中,每一個成員變數盡量只存放一次,採用 final
對每個成員變數檢查只初始化一次。
|
|
Lambda 函數抓取的時候都要抓取可推斷出 final
的變數。為了 lambda 而額外宣告一個變數作為 final 的話,應該要額外宣告一個 getter 函數去抓取,不然觀感上會覺得代碼有點多餘。
|
|
函數盡量使用動詞開頭,名詞有點奇怪。
|
|
當名稱對應到成員變數時,就應該一件事情對應一個操作。
|
|
每次呼叫這個函數都要先建立一個例外,然後看裡面會不會丟出來,這個其實很浪費空間和時間建立。可以考慮建立一個特殊 AbortException
來取代預先建立的例外,或者建立一個全局的靜態例外物件來使用。
|
|
這會額外建立匿名類別。如果宣告在一般的成員方法中,還會捕抓成員變數,造成內存洩漏而無法回收的狀況。
|
|
別忘了,Java 中的 wrapper class 可是一個物件,光靠 ==
是不能比較相同與否。雖然對於數值小於某個定值的時候,他們會在常數內存池中取得,讓你覺得一切都好像沒事。
|
|
當觀察者模式 (observer pattern) 的介面應優先考量 attach/addListener,不應該以繼承的方式覆寫 update/fireListener/notify 等函數。
|
|
第二個 else-if 並不會執行到。
|
|
對 Java 而言,有 Objects.equals
可以替代呼叫,又或者在 boolean equals(Object)
中包含 ==
才對。
|
|
對上參數數量是很重要的。
|
|
手動做 if-else 比較快的,建立 hash 可不是這麼簡單,記憶體宣告等因素需要考慮。而它也不屬於只宣告一次的靜態變數,因此並不適合這樣處理。
|
|
if-else 不應該這樣被偷懶的,整個效能都爛了。
|
|
好的命名決定品質。
|
|
有一種被玩弄的感覺 …
|
|
為什麼要做出兩個相同意義的欄位?
|
|
英文不好的我,一度慘死。
|
|
拜託決定一種風格做一件事情。
|
|
初始化的優先順序很重要,多次觸發有可能發生,造成重複處理。
|
|
函數這麼簡單,為什麼 super.method
寫在原本的函數外部。出現不同於 @Override
的函數中實在少見。
|
|
不要註冊一堆空操作。
|
|
這不知道在寫什麼,也沒叫 super.install()
,該觸發事件的都沒觸發,真的沒事嗎?應該是寫錯了吧
|
|
.
開頭的在 Linux 可是隱藏檔案的意思.DS_Store
那個是 Mac 平台上的隱藏檔案,並不需要xxx.csv$
那個是 Microsoft 平台上的 lock 檔案,並不需要xxx.txt~
那個是 emacs, nano, vim 等製造的備份檔案,並不需要test_result.txt
,正常來講會覆寫,傳上去結果沒產生怎麼辦,比對還會過呢。
|
|
拜託用自己的帳號上傳,不小心用到 product_bot
這種全自動的帳號,不曉得是誰修改的內容。
Java 8 開始推出了 stream 介面,搭配 lambda 可以達到函數式編程 (functional language) 的能力,但是也造成非常多的效能爆炸的憾事。尤其是在我們沒有深入了解官方實作的細節時,請別輕易地轉換到 stream 介面。
在 Stream
出現之前,我們可以透過 Iterator
的實作來達到串流的概念,如 guava 庫所提供的 FluentIterable
就是一個替代方案。公司內部則是因為維護問題自己實作了一個自己的庫,因此沒辦法輕易地用 guava 的項目。但其中一點很明顯的不同在於 parallel 並行特性,即使是 FluentIterable
中,我也未見相關的平行操作。
在自己實作庫、人手不足的團隊配置下,直接吹捧去使用現有的函數庫,那麼問題就容易出現在於轉換上。必須考慮「何時我們該去使用」,決定好使用場景,再決定是否要改寫。公司已經升級到了 JDK 11 的,在銜接官方的 stream 實作時,依舊造成顯著的效能退化,甚至還有嚴重的運行問題,導致 dead lock 等問題。
|
|
在 JDK 11 中,可能會無知地不小心在 static initialization block 中觸發 parallel 特性,此時會造成程式無法運行、卡住。這個問題在於 threading 之間調度時的鎖,另一部分則是 lambda 抓取變數造成的。目前在 bug JDK-8143380 回報系統上看出無意解決此問題,因為這是人為撰寫的問題。如果是呼叫大量的函數來初始化參數,很容易遇到不知道哪個函數區塊內偷偷開了平行計算。
像同事都把 stream()
都改寫成 parallelStream()
,不管問題是大是小,這樣改寫有時候就造成莫名其妙的 Bug 發生,造成找了半天才發現問題。如果覺得著官方提供的項目都有公信力,而且不會做很蠢的事情,一直都可以很聰明地在平行與不平行取捨的話,那其實也不用設計 parallelStream()
給我們使用,統一一個 stream()
接口就好。
想到同事如此狂妄的想法「工程師不該考慮資源,從摩爾定律來看,硬體明年就會解決,我們要做出更好的 scalability 的寫法」,讓我整個火氣都上來,無法接受 用而不知 的習慣。在沒人力挺下,只能眼真真地看著代碼被改得亂七八糟。
了解 Stream
基礎實作後,就會明白在沒有平行的狀況下,維護 stage 的狀態會是額外開銷,相較於手刻的 Iterator
,很多沒有必要的狀態紀錄容易在非常簡單的迭代器中暴露。
串流串接又是更可怕的實作技術,請參閱附錄文章 Efficient multiple-stream concatenation in Java。
|
|
串接的實作像是二元樹。如果操作順序不當,馬上會產生一棵偏斜的二元樹,假設我們要取 a
裏頭的元素,則必須從根一路走到葉節點去。如果構造 $n$ 次操作,且每一個恰好為一個元素,蒐集所有的內容,時間複雜度為 $O(n^2)$。
因此,亂改寫遞迴構造器很容易出現問題,不如用原本的回傳值 List/Collection
把要的東西收集好,不支援惰性操作也好,舊的寫法依舊在 $O(n)$ 完成。若要支援惰性操作,使用 StreamBuilder
建立平衡的二元樹也能在 $O(n \log n)$ 最慘複雜度下完成迭代。
這個問題並不是開 parallelStream()
解決,必須從根本的複雜度去分析,
從內建的 Collection
到 Stream
大部分都由官方做得很好,用不著花太多的心思去思考如何實作,直接呼叫相關函數即可。如果工作需要自己的 Iterator
變成 Stream
就沒有這麼好運。必須了解什麼是 Spliterator
,而 Spliterator
是怎麼實作的,會有什麼樣的介面,而它是從何支援 parallelStream
這一項特性。
假設您已經詳讀 Spliterators.java
下的所有代碼,如自動建造函數
|
|
標示不確定迭代器實際運行的元素個數,好比在 stream 的 flatMap()
和 filter()
後,無法得知下一階段的元素多寡。在這些情況下只能透過這一類函數構造。然而官方預設的行為卻造成了嚴重的效能問題,它要求建造一個 IteratorSpliterator
,而為了支援平行特性,平行通常需要透過高效的拆分陣列完成工作分配,因此會呼叫 Spliterator::trySplit
不斷地拆分,從源代碼中我們可以得知在未知大小的狀況下,則建立 new Object[1024]
預處理資料結構,即使在沒有開啟 parallelStream
,依舊按照相同的運行流程。
有人說在 Java 開 new Object[1024]
很快,而最慘情況迭代器就只有一個元素,甚至沒有任何的狀況下,用最蠢的實作只消耗了一個迭代器的宣告開銷,而轉換串流卻消耗了整整數千個位元組,這是個效能損耗。就算 GC 很強,也不代表它能發現這麼複雜的操作,它整整跨足了超過一個函數區塊。
工作中因為繪製處理,需要運算數億次,若每一次都宣告這麼多餘的陣列,那麼莫虛有內存損耗高達幾十 GB,GC 若要運行 10 次,畫面繪製需要多一分鐘都是有可能的,將數秒不到的計算整整拖累下去。
惰性計算是 stream 的主要特性之一,從另一個術語按需計算 (on-demand)。即使是官方提供的庫也有一些小 Bug 造成假的惰性計算。最常見的像是 flatMap
、map
和 filter
混用的時候,最後使用 findFirst
操作。從代碼上,我們會預期差不多就是找到一個元素之前的所有計算,而從回報系統裡面下關鍵字去找,會發現不少多餘的計算發生。
同事老是說「我們自己的庫超慢的,用 stream 比較快。」
言下之意,只是沒有遇到官方庫的 Bug,而你看到了庫的 Bug 卻從沒打算解決。
最常見的迭代器實作,忘了 Lazy Evaluation 的寫法如下:
|
|
很明顯地,透過 findNext()
我們可能只拿了第一個元素,在不需要第二個元素的狀況下,卻耗費了線性時間。
還有一些問題,下一次再聊聊吧。
|
|
Java 特性預設都是透過 setter/getter 的概念來完成成員變數的存取。為了擴充使用不建議直接存取變數。
這毫無設計的函數是發生甚麼事情?名字這麼像是想害死誰啊,而且還都是不同邏輯。
|
|
不是這樣子說完,就用奇怪的前綴解決。把整個動詞完整描述出來會讓你打字很痛苦嗎?
現在我們用一個指令去搜尋建立的 class files。
|
|
居然發現了非常非常多的匿名類別,而且還是爆炸性的嵌套。這意味者當改變一個 class 檔案時,編譯會一次可能產生上百個檔案。
|
|
這也是常常在調適函數時,不知道為什麼會有 IDE 崩潰的情況發生。其實都是上述的寫法氾濫,連帶的內存洩漏問題也很嚴重。事實上,大部分的匿名類別都可以額外地用功能去描述類別,請重新宣告並且命名好。
|
|
這種命名函數比惡名昭彰的匈牙利還兇殘,直接把文件描述寫在函數上,這時候又願意打字了。
請找個稍微中立一點描述方法,等到哪天換了連帶功能,所有相關函數都要重新做過。
|
|
建立 context 的習慣是要寫在同一個檔案中,而且盡可能寫在同一個函數內。
跳來跳去,有時候只有下文,有時候只有上文,不覺得很可怕嗎?萬一中間執行失敗,直接萬劫不復。
|
|
直接把所有重要檢查通通關閉,卻忘了開起來,直接出逃啦。
|
|
大部分都不會錯,但建議採用堆疊的順序撰寫。因為有些保護機制會造成重複工作。
|
|
我不知道.jpg。
|
|
為什麼需要前綴 test?請給我一個理由。
|
|
如果是預期丟出錯誤的測試就算了,但錯誤的實例也是會變動的,這樣的資料夾名字不具有太深遠的意義。