被預告要開始管人的我,今天開始學管理?

「25 歲的年紀、35 歲的外表、45 歲的認知」那便是工程師的最高境界!

在家工作

種花電信

在家工作最痛苦的無非是網路不穩,炎炎夏日更要顧及數據機的穩定。不幸地,卻發生每晚的七、八點固定網路中斷,操起了畢生所學的技術檢修,仍無法理清頭緒。打給中華電信客服的熟練度,在那幾個夜晚裡迅速地提升,對於那一端的《夢中的婚禮》的好感度卻持續下降。檢修技師來了幾次,換了數據機仍然持續斷訊,連帶的 MOD 也跟著中斷。

白天工作 12 個小時用公司筆電都沒事,用個人電腦反倒出了狀況,就像在提醒我該休息了。技師後來也跑累了,

「斷網的時候打電話跟我說,遠端過去檢測」技師這麼說道。難道活在中英夾雜的圈子久了,連基本中文都聽不懂了。
「都斷網了,你要怎麼遠端?」帶有懷疑地口語回覆
「喔,也是」技師恍然大悟地應聲
看來又一個工作累壞的。
「接下來,麻煩斷網的時候您自己扛著筆電去每個中繼節點下指令診斷並紀錄吧」

突然明白,原來我的技術已經成長到可以檢修,後來把 WIFI 小烏龜換了一台,問題就解決了,也許是訊號回沖導致主要的數據機當機,至於為什麼是那個時間點呢?可能跟習慣有點關係,或者是監視器設定的影響。至於,為數據機採購的散熱風扇,就當作促進經濟吧,默默塞回弱電箱。

那些事

最近由於線上購物和種花,還是會忍著接陌生電話 …

「這裡是 XX 銀行,有不錯的投資標的 …」銀行證卷打來
「抱歉,我喜歡現金」 另一端馬上就傳來了嘟嘟聲

「先生您好,我們提供車貸服務 …」來自車貸業務
「抱歉,我都用現金」一瞬間就嘟嘟聲

「您好,這是政府立案單身聯誼 …」來自單身聯誼
「都說了,我用現金」不耐煩地回應

嘟嘟 …,額不是,聽我解釋 …

《小林家的龍女僕》

工作狀況

這幾個月來,又被分配了不少面試候選,有別於先前的實體面談,疫情關係全都改成了線上面談。對我而言,線上面談更能清楚聽懂對方的表達,不受外表因素的影響。不意外地,仍有不少牛鬼蛇神混入其中,對於母校都失去信心。

看著一輪又一輪的自我介紹,對於新人而言,最自豪的大概只剩下碩士論文,變成口試委員的感覺實在不好受。談完都有一種「這樣也研究所畢業?」,而內心則想「資訊領域這麼大,實作算什麼了對吧?這個要進來訓練?」要是我當初這樣做簡報報告,肯定在台上被罵到下台休學。

《小林家的龍女僕》

實在找不到完美合適的人,標準也逐一放寬,結論「原來當年的我,真的是主管所說的『挖到寶藏』」。接下來,不斷地被提醒「如果發覺其他人陷入泥濘,要適時地提醒」,要規劃教學文件,設計 SOP 流程,免得菜鳥們重蹈覆轍。可是名下沒人,說話也不是這麼地有威信,卻又不斷地被說「你要知道,你就是最厲害的,不用這麼謙虛。」但在我經驗裡,更厲害得人可多的呢。接下來,真的要接新人來管,而我才幾歲呢?

《小林家的龍女僕》

新的挑戰

不知道哪根筋不對,突然跑去 O2 版上發文,試試水溫。

沒意外地,來信很熱誠,看到咱的真相卻?就像撲通一聲入水的小石,便也沒有了後續。從相親的那段經歷,體會到若想強行讓兩條線交會,沒有像黑洞那般的吸引力,是無法使其拐彎的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 標題 [徵女] 我要用什麼樣的速度,才能與你相遇。
# 看板 AllTogether
# 時間 Wed Aug 11 22:01:01 2021
-------------------------------------------
# 關於我
1993/172/70 花蓮人 資工碩畢 台北工作 外商RD 有車位/沒車 母胎單 沒貸款
不菸不酒也不帥,宅了不少動畫,就如標題「我要用什麼樣的速度,才能與你相遇」來自
於《秒速5厘米》,日美韓劇都有涉獵,一點點鋼琴,一點點桌球。遊戲只有始終如一的
楓之谷。
基本上在家工作一年多了,也開始自己採買與下廚,不怎麼偏好美食,味覺遲鈍。大部分
的技能點都跑去了專業技能上,距離成熟的社會人還有段距離,最近卻要在職場上煩惱人
與人之間的溝通。
國外出差幾次,目前沒有旅遊愛好。由於工作內容,習慣考慮周詳、思考最壞狀況,一旦
要做就不能做差的處事態度。
目前在林口附近,希望能交些朋友,彼此成長。
# 關於你
* 有專業技能養活自己
* 不菸不酒

影集心得

《火神的眼淚》

台劇 《火神的眼淚》 像個刁民現形記,太過真實不敢直視。劇中許多有理說不清場景,離我們並不遠,應付著普羅大眾眼中的責任外,還得背負那不為人知的壓力。

研究所當助教時,體會只要敲錯一個指令,兩百名學生的成績權益會受到影響,平常提心吊膽處理著伺服器。沒 SOP 流程,也沒相關經驗,只靠過往經驗謹慎行事,每敲一個鍵前都要設下好幾條防線,最後那一刻,手還會抖。

工作也會相同感受,想到能理解接下來的實作正確性的人,估計是沒有了。但又為了交付幾個月前的承諾,這一個方法必須下去,目前看不出問題,不代表上戰場時不會發生問題,猶豫不決,應付客戶的 AE 得收砸出來的爛攤子。反而越接近收尾,決策就越慢 …

《攻敵必救》

《攻敵必救》 (Miss Sloane, 2016)

「有時我們行動不是為自己,而是相信是做對的事。」
「我不知道界線在哪裡,我一直不知道怎麼衡量」
「我寧願有正常生活,也不要這個技巧」

《所以我和黑粉結婚了》

韓劇 《所以我和黑粉結婚了》 ,出戲的配角台詞 「 我有一個新企劃,你沒有興趣一起?

Read More +

Company Ghost Story 公司鬼故事 12

Java

Fancy OOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MSTAlgorithmEngine {
JButton mApplyButton; // why, ???
JPanel mUI; // why, ???
}
class DiffRecord {
Action mMergeAction; // why, ???
Action mShowAction; // why, ???
}
class Rectangle {
void normalize() {
new NormalizeAction().actionPerformed(new ActionEvent(ActionEvent.PERFORMED));
} // I cannot deal with this shit anymore.
class NormalizeAction extends AbstractAction {
// real implementation for model class
}
}

資料歸資料,算法歸算法,別把 UI 牽扯進來。

Shit Cache

1
2
3
4
5
6
7
8
class Engine {
private static CacheStructure sCache;
public void exec() {
resetCache();
// fill cache;
} // but, when will you clear sCache, sCache = null?
}

快取的污染啊,那些殘存的資料依舊卡在記憶體內,是無法被 GC 回收的。反而,造成下一個運行的更容易出現記憶體不足。

Shit Cache in Class

1
2
3
4
5
6
7
8
9
10
11
class Dbo extends DbObject {
private CacheStructure mCache; // for internal recursion, why this thing is here?
public Object getSomething() {
// init cache
return internalGetSomething(parameters);
} // but, when will you clear cache, cache = null?
private Object internalGetSomething(parameters) {
// ...
}
}

別亂放你的資料結構,這關資料庫物件什麼事?

Read More +

Java JNI GC Thread Error (EINVAL)

Keywords: JNI, Java, GC Thread, add_workers, static library, Thread Error

Solution: -XX:+AdjustStackSizeForTLS from JDK 14

Description

We cannot launch GC thread of JVM from JNI. And, it shows

1
[os,thread] Failed to start thread - pthread_create failed (EINVAL) for attributes: stacksize: 1024k, guardsize: 0k, detached.

The main reason is the glibc bugs for native C code issues. For example, we allocate thread-local variables by static library linking.

1
2
3
__thread int buf[1048576];
thread_local int buf[1048576];
#pragma omp threadprive(buf);

And then, glibc 2.12 will move them on stack, and require minimum stack size at least 1024k bytes. But, the default configuration JVM of JDK11 will minimum stacksize to be 1024k bytes as default in Linux OS. Therefore, the pthread_create throws the error EINVAL for specific stacksize setting.

Options

https://www.oracle.com/java/technologies/javase/14all-relnotes.html

The glibc library allocates some thread-local storage (TLS) in the stack of a newly created thread, leaving less stack than requested for the thread to do its work. This is particularly a problem for threads with small stack sizes. It is an inherited issue from a well-known glibc problem, ‘Program with large TLS segments fail’ [0] and has been observed in Java applications. In one of the reported JVM failure instances, the issue manifests as a StackOverflowError on the process reaper thread, which has a small stack size. The java.lang.Thread constructor enables users to specify the stack size for a new thread. The created thread may encounter the TLS problem when the specified size is too small to accommodate the on-stack TLS blocks.

In JDK 8, a system property, jdk.lang.processReaperUseDefaultStackSize, was introduced to address the TLS issue only for reaper threads. Setting the property gives a bigger stack size to the reaper threads.

To address the issue for all threads, a general purpose workaround was implemented in Java which adjusts thread stack size for TLS. It can be enabled by using the AdjustStackSizeForTLS command-line option:

When creating a new thread, if AdjustStackSizeForTLS is true, the static TLS area size is added to the user requested stack size. AdjustStackSizeForTLS is disabled by default.

Conclusion

整合進大型程式時,要多觀察靜態編譯的部分,如果有一些 TLS 的宣告,則會增加其他預設執行緒的最小使用堆疊大小。而 JVM 又傾向使用最少資源去完成,則很容易在計算最小資源時發生問題。

如果去查閱 JDK15 的 代碼 os_linux.cpp 時,有一段到動態鏈結函數庫 glibc 的詢問最低需求來解決此問題。這一段牽涉到 hack 技巧,偷偷利用 symbol 爬出私有函數,不算正常操作行為。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// On Linux, glibc places static TLS blocks (for __thread variables) on
// the thread stack. This decreases the stack size actually available
// to threads.
//
// For large static TLS sizes, this may cause threads to malfunction due
// to insufficient stack space. This is a well-known issue in glibc:
// http://sourceware.org/bugzilla/show_bug.cgi?id=11787.
//
// As a workaround, we call a private but assumed-stable glibc function,
// __pthread_get_minstack() to obtain the minstack size and derive the
// static TLS size from it. We then increase the user requested stack
// size by this TLS size.
//
// Due to compatibility concerns, this size adjustment is opt-in and
// controlled via AdjustStackSizeForTLS.
typedef size_t (*GetMinStack)(const pthread_attr_t *attr);
GetMinStack _get_minstack_func = NULL;
static void get_minstack_init() {
_get_minstack_func =
(GetMinStack)dlsym(RTLD_DEFAULT, "__pthread_get_minstack");
log_info(os, thread)("Lookup of __pthread_get_minstack %s",
_get_minstack_func == NULL ? "failed" : "succeeded");
}
// Returns the size of the static TLS area glibc puts on thread stacks.
// The value is cached on first use, which occurs when the first thread
// is created during VM initialization.
static size_t get_static_tls_area_size(const pthread_attr_t *attr) {
size_t tls_size = 0;
if (_get_minstack_func != NULL) {
// Obtain the pthread minstack size by calling __pthread_get_minstack.
size_t minstack_size = _get_minstack_func(attr);
// Remove non-TLS area size included in minstack size returned
// by __pthread_get_minstack() to get the static TLS size.
// In glibc before 2.27, minstack size includes guard_size.
// In glibc 2.27 and later, guard_size is automatically added
// to the stack size by pthread_create and is no longer included
// in minstack size. In both cases, the guard_size is taken into
// account, so there is no need to adjust the result for that.
//
// Although __pthread_get_minstack() is a private glibc function,
// it is expected to have a stable behavior across future glibc
// versions while glibc still allocates the static TLS blocks off
// the stack. Following is glibc 2.28 __pthread_get_minstack():
//
// size_t
// __pthread_get_minstack (const pthread_attr_t *attr)
// {
// return GLRO(dl_pagesize) + __static_tls_size + PTHREAD_STACK_MIN;
// }
//
//
// The following 'minstack_size > os::vm_page_size() + PTHREAD_STACK_MIN'
// if check is done for precaution.
if (minstack_size > (size_t)os::vm_page_size() + PTHREAD_STACK_MIN) {
tls_size = minstack_size - os::vm_page_size() - PTHREAD_STACK_MIN;
}
}
log_info(os, thread)("Stack size adjustment for TLS is " SIZE_FORMAT,
tls_size);
return tls_size;
}
Read More +

Company Ghost Story 公司鬼故事 11

Java

Over Overload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class OhMyGod {
public void run() {System.out.println("A");}
public void run(String t) {System.out.println("B");}
public <T> void run(T[] t) {System.out.println("C");}
public <T> void run(T t) {System.out.println("D");}
public static void run(String... t) {System.out.println("E");}
public static void main(String[] args) {
new OhMyGod().run();
new OhMyGod().run(new String[0]);
new OhMyGod().run("1");
new OhMyGod().run("1", "2");
}
} // AEBE, little strange

One-Format Multiple-Ways

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class FileIn {
private boolean isCSV; // why not enum
private boolean isXML;
private boolean isJSON;
public void setIsCSV(boolean e) {isCSV = e;}
public void setIsXML(boolean e) {isXML = e;}
public void setIsJSON(boolean e) {isJSON = e;}
public void import(File file) {
// why not if-else
if (isCSV) CSVIn(file); //???
if (isXML) XMLIn(file); //???
if (isJSON) JSONIn(file); //???
}
}
Read More +

疫情下的工作變化

居家工作

去年開始越來越少去公司的我,遇到四月的疫情爆發,便順理成章地不去公司。以前還會偶爾跑一趟台北,完全不用去公司的感覺挺不錯的,但是其他同事可能就無法接受,畢竟租屋或住家並沒有理想的工作環境,沒有相應的螢幕、鍵盤、滑鼠麥克風等配備,筆電雖然五臟俱全,但在開發效率上就顯得不足。

更無法想像的是,除了工作日以外,一般居家都沒有這些基礎配備,這群現充們都在做什麼?實在令人稱羨。反觀,對我而言,那些是維持生命的器材。想著搬離租屋環境,於是下訂了 Dell P2721Q 這款 4K 螢幕來開發,得處理渲染效能與硬體上的問題,就得多一種硬體設備來測試,連帶修整了耳機麥克風,好好地進行整線配置。

公司終究是不屬於我的地方,座位上的雜物一天比一天更空。沒法像其他人,把公司當家,可以堆一大堆用品。在家有自己的小天地,好好投資的感覺相當踏實,至少省去了以後搬走的困擾。

薪資談判

「不對,是能有多少思考本質的能力」—《東大特訓班2》

從去年開始,就不斷跟主管講薪資太低,不管是我的、還是同事的,一直找不到中意的人才,肯定有它的道理在。參加會議的時候,Meet.jobs 公司開薪資起跳就兩百萬,先不論中間有其他額外的契約條件,起碼還是有可談的條件。年底又有一波 Google 大批招人,那時跟主管警示了一次,公司到底要收怎麼樣的人,只在意找人,看起來留人是未來的課題。難道,只想辦法讓台灣分公司突破五百人,還開個慶祝會,不考慮到底是找什麼樣的人嗎?在台北分布的我們,卻感受不出來那個向心力,也沒有什麼實體慶祝會。

不少半導體大廠很賺錢,像 MTK 就不斷地在新聞打廣告,碩士起薪 150 萬、博士起薪 200 萬。我們做了什麼、能做什麼?將這些訊息向上呈報,也不見聲響。早些年,說幫我談有博士資格的起薪,現在回過頭來看,只能算勉強達標吧。

外傳我們部門很挑人,的確有很多人工智慧、機器學習、深度學習的 green hand 被我們刷掉,一開始的確按照 Google 等級的難題在找特殊領域的奇才,根據基礎底子標準來刷掉。現在面試只考一個費波納西數列,牛鬼蛇神馬上現身,leetcode 上的題型背一背是趨勢,那就偏不考那種制式化的題目,沒有明確要怎麼做,就沒有標準答案,就看心中的定見有沒有特色。

的確,這幾個月來陸續有人離開,但仍不想隨便補人,就怕未來鬼故事太多。可能不會有鬼故事,就怕產品原地打轉。換換別人來面試吧,線上面試這種考驗人心的方法,沒辦法預設人性本善,被騙了太多次,最後工作量暴增的我,沒有特別在期限內有特別突出的開發,反倒收拾不少殘局。

工作突破

半導體製程的演進帶動了一堆需求,不只效能要好,還要記憶體用量正常,俗話說一點就是要跑得動。越來越多客戶,開始規劃要將近數億的數量級的資料庫管理,本身早已處於高度壓縮的資料庫設計,但會這樣的設計影響即時的運算效能、開發難度、以及設計彈性。

因此,最近幾篇的技術文章都在探討記憶體的使用,甚至靠這些設計概念,滿足了原先的彈性,去除了雜湊表,效能還提升,內存使用更少,數方雙贏的局面,讓產品走得更久。目前產品已經滿足所有需求下,回歸了正常的 capability。

不知道為什麼,開心不起來。

《86 不存在的戰區》

Read More +

Company Ghost Story 公司鬼故事 10

Java

Compare Function

1
2
3
4
5
6
7
Collections.sort(arr, (x, y) -> (int) (x.cost - y.cost));
Collections.sort(arr, (x, y) -> Long.valueOf(x).compareTo(y));
Collections.sort(arr, (x, y) -> (int) Math.round(x.cost - y.cost));
// why not use official methods?
Collections.sort(arr, (x, y) -> Double.compare(x.cost, y.cost));
Collections.sort(arr, (x, y) -> Long.compare(x, y));

別鬧了,會 overflow/underflow,數量一大,每一個排序都在丟例外給我啊。

Partition Function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Optimizer {
private int avgX(ArrayList<Point> pts) {
ArrayList<Long> x = new ArrayList<>(pts.size());
for (Point p : pts)
x.add(p.x);
return avg(x);
}
private int avg(ArrayList<Long> xs) {
long sum = 0;
for (Long x : xs)
sum += x;
return sum / xs.size();
}
}
// ... why not merge avg() into avgX()?

每次都要額外建一個陣列,而且這個函數只有被內部的單一函數呼叫,函數切這麼細,但是效能卻爛掉。

Try-Catch vs. If-Else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Parser {
void addChar(char c) {
try {
que[index] = c;
} catch (ArrayIndexOutOfBoundsException e) {
doubleArray();
que[index] = c;
}
index++;
}
}
// why not just write if-else statements, it's really hard to maintain your code.
class Parser {
void addChar(char c) {
if (index >= que.length)
doubleArray(index);
que[index] = c;
index++;
}
}

如果在修改這幾段代碼的時候,還得經常考慮例外處理是怎麼做的,這寫法真的太奇葩。要是開剖析器去檢查,會經常看到例外出現,那到底是好事還壞事?

Read More +

前思後想 新居落成

截至這半年來,人生成就上沒多大的變化,除了工作也沒有其他說得上嘴的娛樂。

前思後想

相親了幾次,過年那段時間,對方趁著女兒回家一趟,就順道提個活動認識一下。但對方還沒大學畢業,大概也想不到那一塊去,而我這個宅宅更不是女性首選清單。活動說要聊一聊,但專心在完全不擅長的活動內容上,讓我比較舒心。畢竟,那些小女孩還有大把的時間過濾清單,又聽女方經常說「XXXX,沒別的意思」此話一落,我就真的沒什麼意思了。

之後在咖啡廳見了一次,在對方家人婚禮也見了一次,被拉去合照,被抓去婚宴台上參與活動。此時,內心滿是焦慮,對方似乎就沒那個意思。在這幾次過程中,感覺都是對方父母滿意,實際上一來一往感受不出任何互動。反倒是,與未曾蒙面的女網友討論相親一事時,互相揶揄相親經驗,這樣算起來比較像朋友。

「交個異性朋友很難吼,之前還笑我 …」
「那我們當初怎麼當朋友的?」
「我也不是很清楚」

「我也不是很清楚」—《悠悠哉哉少女日和 Nonstop》

「相較於幾年前,你也變化得很多,聊起來的感覺差很多。」對方說道

倒是覺得自己沒什麼變化,只是更會搭唱罷了。沒機會收到對方的抱怨或幹話,其他話題可是一竅不通,當然感受不一樣。想想以前的話題,不是玩就是花錢,我完全搭不上邊。

新居落成

今年一月底的時候,決定買了一間小華廈,三房兩廳兩衛含車位。主管不給力的情況下,當然是買不起大台北區,反正也遲遲交不到女友,還是得過自己的生活,老是跟著高中同學在台北合租,每個都帶女友一起生活,日久傷情。過得很簡單,卻稱不上好,找不到一個理由過得更好。縱使街道上車水馬龍,卻無比孤單。從去年夏天一個人運動跌倒,提著半面血痕走了一大段路回租屋處,無法言語傳達的感受,更促使著想離開的心境。

在疫情剛開始的那個時候,便約了房仲看了幾十間,也在盤算手頭上有多少錢,到底要不要貸款,要的話要貸多少?那時看的房子,還無法符合經濟條件,突然間題目馬上難起來。不同於同事們一開始就生活在台北,對地區生活很熟悉,更不需要在外租屋。大多數搬出去的原因都是要跟女友同居,或者想嘗試獨立生活,心生羨慕啊。要是我老家在台北,大概會被罵得連這點錢都不省,有家還不住。

到了一月中,帶著爸媽又跑去看了一輪,馬上就下決定。當天斡旋,隔周簽約,原本預計 45 天內交屋,但中間遇農曆新年,又多拖延了一週。不斷地與房仲、代書接洽相關事宜,在每一個階段跑銀行處理履保帳戶。再跟家人借點錢,畢竟身為一個工程師的貸款利率還是高了點,並且還是浮動利率。此時都在懊悔,要是有一個軍公教的另一半,就不用這麼傷腦筋。

「一直以來,我都是急切地想讓自己一個人解決所有問題」—《無職轉生》

不貸款情況下,再三確定不跑銀行貸款,三催四請提早一周,在三月中交了屋。交屋前一週把網路申請好,交屋當日下午,床櫃子書桌都請家俱行老闆一次服務到位,同樣地,晚上家電冰箱電視洗衣機,全國電子安裝人員一次做好。家裡要求做事要果斷下決定,前幾周跑了不少店家去想要怎麼布置,又跟房仲約了幾次量測房子格局。最後,有幾處稱不上滿意,但以我一個人的人生經驗下這類決定,已經盡力了。

當天就這麼在新家睡了,隔天再跟老爸北上跑一趟,一次把租屋處的雜物全部清空。爬老公寓五樓,真不是鬧著玩的,還得扛著自己大包小包的雜物。很早就設想自己會搬家,租屋就一直不敢買太多東西,但仍然要爬個幾十趟,不意外地,隔天的腿抬不起來。

《悠悠哉哉少女日和 Nonstop》

熟悉 一個人的真生活。一開始倒垃圾時,左鄰右舍都很在意地問「一個人生活還得煮飯倒廚餘?」這一個問題,我也有。沒有台北那樣繁榮,下樓就有得吃,在這裡自己煮點也沒那麼誇張吧?一直都是粗養,美食品味造成影響,心理狀態沒有問題。陸續的幾個月裡,處理一些家俱的毛毛角角,浴室的排水排空清潔,聯絡安裝冷氣與清潔。跑了好幾趟五金行,處理居家生活細節,得全部親自動手做。

開個宴席請公司同事們吃個飯,約離職的大姊姊回來聚聚,其他離職的小夥伴都在美國就有點小難過。再邀邀一些朋友來家裡作客,聊聊一些生活經驗,聽聞學長們只想在台北買,也即將在台北市買房子,突然發現自己也不是做了大決定,都不及學長賺的一半,痛心明白,難怪我還找不到另一半。

當然相親一事尚未結束,偶爾還是有一點風聲,就是一點風聲。就連家俱行老闆都在幫我安排對象,也都是對方父母覺得不錯,而對方不是在學,就是職場菜鳥,自然而然沒什麼反應。以這幾個對象相處的經驗,發現都有一個共通點,儘管當下馬上回覆,都是每天定點定時才會得到訊息,隔天還算好了,有時隔幾周應該是排到深淵去了。

謝謝你,讓我知道我還不夠好。

交接租屋

一月時說要買房子離開原本的租屋處,室友都說至少還要半年,覺得不可能這麼快,似乎都沒打算煩惱。三月一離開,租賃契約的一半都不到,只能麻煩其他室友找新室友來補位,沒想到房東馬上就說要漲房租。直到四月補位,五月底一知道調漲的金額,我想在這個疫情中做這樣的調整,多半不合情理,也許是看我窮都不漲吧。

合租也有不少缺點,儘管自己自發性地打掃,代收信件、繳納水電瓦斯費用、處理網路配備等,公共區域多出來的物品,都不是屬於我的卻一直被嫌很亂,自己用的電器放在房裡電都沒插,都可以被房東罵危險。明明一開始租屋就有的地板污漬,卻一直被指責說是我們搞出來的,自己一個人花一個下午,獨自坐在地板上清潔。室友堆積的雜物,一個人花了好幾天分批爬上爬下拿去垃圾車丟掉。

房東其實沒有說可以長時間養貓,至於室友鑽空子的行為,導致整個生活空間都有貓毛,不少地方被抓破,紗窗被抓破還要我補,你不怕蚊子,我怕啊。就看誰的忍受度低會先掃地,自然也會有自掃門前雪的情境,當然這也算沒什麼生活常識吧,沒有特別的惡意。長時間把濕抹布放在木頭地板上,叫完瓦斯,戶外燈也沒關,紗窗也沒拉上,對其他人可能覺得沒什麼吧。小事情積多,就見仁見智了。

出個遠門

生活距離台北有段路,其實有邀約也很懶得去趟台北。四月底,有人約我去文博會看展覽,想順道吃個飯。遲遲沒有答應,只是因為六日都在處理修繕雜事,不確定還有什麼沒有做。那天一大早,發現事情都忙得差不多了,就起身出發。能約什麼人呢?想著定點才回覆的任務,等到有消息就隔天了,淡定地一個人出發。

有人會很好奇是不是約妹子出門,要說有也算有,但要說沒有,也算沒有。逛完展覽又跑了趟淡水,想著幾個月前一個人跑到了淡水,想著妹子約不出來,一個人去那邊看看房子和生活機能,提前看看餐廳,說不定哪天有機會約人吃飯,打量著很多很多的事項,可惜都派不上用場。而跟一些人一起去淡水,非常簡單地在路上走走,也不需要吃什麼特別的餐廳,隨處逛逛商店聊聊幹話,坐在河畔旁等待,就這麼樸素。

Read More +

Java 優化內存使用 組合篇

整數映射 (Integer Mapping)

算法經常會使用整數映射到物件,而整數範圍就會影響到內存。為了技術性能 (capability),直接宣告

1
2
Map<Long, Object> a;
TLongObjectMap<Object> b;

以應付未來變化。考慮到鍵值欄位,實作上建立 $\mathcal{O}(n)$ long[]。在初始狀態下,大多情境只使用 int[] 的情況。為此,我們考慮分流使用

1
2
3
4
class MMap<T> {
TLongObjectMap<T> longMap;
TIntObjectMap<T> intMap;
}

使用這樣子的概念,通用狀態可以降低 $50\%$ 內存,均勻分布情況則可以降低 $25\%$

陣列替代 (Array Substitute)

前一篇,我們提到了將 ArrayList 換成最原生的 T[]。更進一步,以前寫 C 的時候,壓根不在意 T[].length 這一個需求,因為題目已經寫死在某個全區變數裡。可在 Java 裡頭,我們宣告不出純粹的陣列,只能硬生生多出 $4 \; \text{bytes}$

1
2
3
4
class Scheme {
K[] keys; // as known, keys.length = 10
V[] vals; // as known, vals.length = 10
}

換個方向思考,那朝著減少試試。進行陣列雙向合併

1
2
3
class Scheme {
Object[] keyval; // [key0, key1, ..., val1, val0]
}

Scheme 物件中,我們減少了 $8 \; \text{bytes}$ 的額外資訊。但在使用時, casting 是個開銷。

欄位轉移 (Field Translation)

1
2
3
Map<T, String> map;
map.put(a, "MARKED");
map.put(b, "UNMARKED");

當我們還沒有仔細規劃結構時,直接用 has-A 的精神去寫第一步。回過頭來,就會造成不少零散的映射出現。如果映射使用雜湊實作,而不是樹的話,整體的記憶體效率會低個 $33\%$,因為無法塞滿所有的 hash table 中的桶子。

1
2
3
4
class T {
...
Object userData;
}

那為了提升記憶體效率,先檢查是不是所有的項目,幾乎都在這個映射裡面。如果是的話,將這個欄位轉移到目標類別上。看起來很不美觀,但進一步的資料佈局是很可觀的,甚至可以比雜湊 $\mathcal{O}(1)$ 更快的存取。

卸載策略 (Offloading Strategy)

在平行處理中,我們可以劃分成不同數量級的策略。在小規模下,嘗試先以 CPU 計算,若發現更大規模,將資料卸載給其他異質計算單元來完成。同樣的道理,對於資料結構也能更近一步地劃分。

如集合實作:

1
2
3
class MSet {
Object mRaw; // size < 8: LinkedList; otherwise, HashSet
}

由於 Java 中的 HashSet<T> 並不是單一欄位的實作,實值為 HashMap<T, T>,相較於 C/C++ 的狀態,空間多了兩、三成。我們可以簡單使用 LinkedList 取代 HashSet,並且避免小規模數據中未能填滿 hash table 的狀態。在元素個數為一時,串列的記憶體效率 $100 \%$,但雜湊集合只有 $12.5 \%$。換句話說,有時候數據分布很極端,內存用量就差了十倍。

Read More +

Java 優化內存使用 結構篇

概要

首先,針對 JVM 這一類的語言設計,所有事物皆為物件,那麼就表示每一個物件都必須額外紀錄 object header,也就是說一個物件在 64 位元的作業系統環境下,通常會額外帶有 12 bytes 資料 (mark word:4 bytes 和 klass word:8 bytes),但 64 位元計算機架構需對齊 8 bytes 的規範,使得每一個物件都大上許多。

例如,一個 struct Point 在 C 語言裡面,

1
2
3
4
struct Point {
long x; // 8 byte
long y; // 8 byte
}

我們輕易地就明白單一物件大小為 16 bytes,而在 Java 撰寫時,卻會變成 $32 \; \text{bytes} \;(\text{header:}12 + \text{x:}8 + \text{y:}8 + \text{padding:}4)$

1
2
3
4
5
class Point { // object header 12 byte
long x; // 8 byte
long y; // 8 byte
// padding 4 byte
}

這樣看起來並不妙。相同的演算法、配上相同的數據,Java 處理時的內容硬是比 C 還多出整整兩倍。物件導向有其優點,必然存在一些缺點,可以很彈性地開發、豐富的權限管理、方便追蹤剖析,卻容易在非常基礎的大量計算中暴露記憶體方面的問題。

打包物件 (packed object)

對於基礎物件,就只能倚靠撰寫 JNI 或者修改 JVM 的參數,來降低每一個單一物件的大小。對於底層不夠熟悉的開發者而言,還是可以透過一些實作方法來完成。但必須犧牲一點物件導向的方法論,喪失維護性,面對真實問題。

例如,我們儲存一個矩形,需要一個左上角和右下角的點。

1
2
3
4
class Rectangle {
Point a; // pointer: 8 bytes
Point b; // pointer: 8 bytes
}

資深工程師總會很直覺地 重複使用,這個原則並沒有錯,但對於大量的基礎物件而言,宣告一個矩形實際上會占用 $32 + 2 \times 32 = 96 \; \text{bytes}$。接著,老是有人跟我唱反調說「效能分析工具 (Profiler) 只給出 $32 \; \text{bytes}$,你肯定算錯了!」別忘了,你寫的不是 C/C++,是一切皆為指標的 Java。

那我們要怎麼打包物件呢?或者說 攤平,目標要減少 Point 多出來的 object header。

1
2
3
4
5
6
class Rectangle {
long lx;
long ly;
long rx;
long ry;
}

返璞歸真,直接將欄位往上提取,破壞一點基礎建設,在一些 API 上犧牲一點效能。這樣就變成了 $48 \; \text{bytes} = 12 + 8 \times 4 + 4 \; \text{bytes}$ 的物件。如此一來,減少了一半的內存用量。

原始型別 (Primitive Type)

在很多不經意的操作下,很容易將原始型別 (primitive type) 儲存成物件型別 (object type),也就是物件導向最容易造成的問題,從整體用法來分析,並不影響程式正確性,但會造成記憶體用量飆高。如 long 原本為 8-byte 物件,轉換成 Long 會變成 24-byte 物件。

繼承與泛型影響

1
class Version extends Pair<Long, Long> {}

當想重複使用類別,透過繼承的泛型很容易自動轉換成物件型別。這個問題在 C# 內並不存在,CIL 能允許宣告 Pair<long, long>,並且不透過型別抹除,分別建立相應的代碼,能解決此問題。在 Java 上要解決這個問題,只能更樸素一點。

1
2
3
4
class Version {
long major;
long minor;
}

代碼多了一點,取而代之的是記憶體量減少,物件轉換次數的降低。

多形使用

1
2
3
4
List<Object> list;
list.add(1);
Map<Object, Object> map;
map.put(2, 3);

多形操作下,將好幾種不同類別的物件一起使用,隱式轉換問題就會發生,一般要追到底層才知道最後發生什麼事情。由於 Java 官方的庫並沒有預設大量的原生型別的資料結構,我們只能透過像 trove4j、eclipse collections 等插件來補足,必要時自己刻一個。

1
2
3
TLongObjectMap<T> a;
TLongArrayList b;
...

資料結構 (Data Structure)

雜湊表 (Hash Table)

在大多數的實作下,雜湊表佔據線性空間 $\mathcal{O}(n)$,存取時間 $\mathcal{O}(1)$,理論分析上也近乎完美。在處理碰撞的技巧上,影響常數級別的空間用量。通常分成以下兩種

開放定址法 (Open Addressing)

trove4j 和 eclipse collections 採用此方法。

  • 使用記憶體空間最小
    僅有兩個陣列 key[]value[],沒有多餘的 object header。
  • 存取常數大,調整時間較長
    通常會有兩個以上的 probing 操作,中間穿插刪除操作時,容易造成退化。

串鏈法 (Chaining)

jdk 預設採用此方法。

  • 使用記憶體空間最大
    HashNode {val, next, prev} 佔據了大部分的記憶體,且大量的 object header 出現。
  • 存取常數較小

從穩定性分析上,串鏈法在某種程度上可以複合許多結構,如 unrolled linked list (bucket) 或者是 binary tree 來降低最慘複雜度。而開放定址法,雖然有記憶體用量小,快取能力更高,但數據量一大,很容易在 rehash 的階段花太久的時間,而且不容易做到刪除操作。

固定長度陣列 (Fixed-length Array)

Java 開發久了,連宣告陣列都很吃技術。有時候,並沒仔細探討固定長度與不固定長度的差異。劈頭就寫一個

1
2
3
class Scheme {
ArrayList<Field> fields;
}

這樣的設計擴充性佳,具有彈性。在底層設計時,卻會發現 ArrayList 至少包含了 sizeval[] 兩個欄位,而 val[] 包含了真正的 array pointerlength 兩個欄位。如果已知固定長度且不再變更,或者變更的使用量極低,且能保證 size == val.length,那不如直接宣告

1
2
3
class Scheme {
Field[] fields;
}

如此一來,一個 Scheme 物件便能減少 $16 \; \text{bytes}$。更進一步,我們可以利用封裝 (Encapsulation) 和多型技術,再降低 $8 \; \text{bytes}$

1
2
3
4
5
6
7
8
class Scheme1 {
Field f1;
}
class Scheme2 {
Field f1;
Field f2;
}
...

結語

根據實際應用的數據分布,對最常用的物件進行優化。醜了點,但能解決問題。雖然並不是黑魔法,更能了解實作。

Read More +

二八進展

工作狀況

繼去年十月升職後,得處理的工作難度也更加艱難。開會密度上升了一個層次,工作時間也因為在家辦公而拉長,在本來就沒加班費制度的工作崗位,這些早有心理準備。

工作挑戰到一個懷疑人生的地步,每天可能五、六點起床,七、八點就坐在電腦前工作十二小時,不然抽不出時間寫代碼。開會時間外,得處理不少回信和規劃一些未來架構,著手運行實驗的過程,還得不斷地補教學文件給新手入門。不得不說,不管是電機系還是資工系,著手 EDA 都有不同面向的挑戰,這裡的後端並不像外界所理解的後端,全部都要親自實作,不是像了解用法或原理道理後,單純組合使用的那一類型。

在年底的幾個月中,幾乎每天都開會,從早到晚的會議從前一個星期就開始排到下周,突發會議也再所難免。身為工程師,連續聽一個小時的話就很疲倦,一天至少聽四、五個小時,到最後都是精神渙散。

轉職計畫

因為工作壓力過大,起初很想換工作,請假參加了今年度的 JCConf Taiwan 2020 (Java Community Conference Taiwan),原本想說問能不能報公司帳,問了之後還要寫一堆提交文件申請,不是送件去新竹就是上海,就像寫國科會計畫一樣麻煩,只好自掏腰包去。不意外地,現場有不少媒合工作機會的攤子,如像 meet jobs 發了一張薪資報價單,放眼望去不少資深工程師職位有年薪百萬等級。這時候,就得想一下工作內容的難易程度,畢竟一點也沒有外界所稱的後端經驗,做基礎科學久了,多面向應用層級的後端似乎有挑戰。

之前 Google 和 Synopsys 來找人的時候,感覺就像面試從 104 的狀況一樣,老實說我還挺不喜歡 HR 在不確定實際工作內容的狀況下,諮詢轉換工作意願。自己就面試不少 HR 找來的,大部分都很難適應高強度的工作,而且行為準則也不好評量出來。如果是在公司的小產品中可能還無關緊要,在關鍵第一線產品中,每一個環節都很關鍵,能自我成長和要求才是重要的。不幸地,對大部分的人而言,只不過是為了生活而工作,那可能就不適合做第一線的挑戰,秉持「差不多就好」的心態,卻讓我花了太多時間處理那些瑣事。

想當然耳,更別說被 HR 以這種方式邀約,準備面試的工作相當地耗費精力,而且不知道有沒有稱得上工作,若只是收進去放置,吸引力就不高。主要是別因此造成其他同事的負擔。

另類工作

先前提到的高中生的 APCS 考試,實驗室學長創建了 「松鼠狐狸資訊學院」,也讓我從中接觸了不少外界資訊。在公司工作充其量也是替老闆工作,誰也不會知道那項目你有參與並有重大貢獻。而教職能影響的方位與工作不同,同時也會影響到不少人的一生。

「應該沒有經歷過社會吧」-《咒術迴戰》

那看看現今補習班的狀況,就會感嘆很多老師並沒有實際工作過,也不知道那些知識學來是為了什麼,在這種情況下就像考大學一樣,高中學了不少學科,上了大學全忘記。這全都是因為不知道目標與應用,當然就無法連接所學的知識。

還有更慘的是最厭煩的錯誤,做過助教就明白哪一些錯誤不該發生,身為老師就像大學只學了幾年就出來教人,沒有融會貫通的狀況下,按照書本念很難讓學生啟蒙。

學程式的確可以採 非監督式學習,就像 對抗網路 一樣,因為程式整套是很容易自我反饋和評估的。但前提是程式語言的選擇,C/C++ 和 Java 各有自己的盲區,訓練所需時間差異極大,視野深廣也不同。可謂魚與熊掌不可兼得,才會有補習班這種 監督式學習,大幅度縮短學習成本,才能對整個社會文化的進程加速。

薪水的部分也不亞於公司社畜。但目前社會歷練尚未成熟,還不敢貿然辭去工作。

另類邀約

在中央大學時,與一脈單傳的學長們一起活動,畢業後各奔東西。有次來個全部跨一屆的大學長們聚餐,才知道最終的目標不見得是 Google,也可以為了自己個某些理想或機緣而努力。

隔了幾年後,大學長從大公司離職,跑去做遊戲開發。就我所知,那一行八成產品都難以賺錢,而且橫跨好幾層的美術、物理領域。學長跑來問我對遊戲有沒有興趣,心想在大學做了不少遊戲相關的技術,草創階段的難度和時間壓力跟現在不能比。

對這次的邀約,內心是雀躍的,表示先前的技術和開發經驗有被關注。但在大學課程中卻被老教授抨擊設計不符學術理論,那時對於跟人有關的開發有點動搖,沒有確切理論可以證明方向,必須全靠感覺來抓緊機會。

目前與理念有點差距,心理準備未到。

轉角分支

相親發展

在一個沒有公司內部聯誼機制的環境,找到另一半只能靠自己。如在聯發科 (MTK) 還有一個特殊的交友佈告欄,外界的男女都可以跑上去諮詢,學長在那還可以煩惱要不要寄送好友邀請,Google 也有類似的內部產品。

這些事情很現實,對方要求就是身高、公司行號,這個在高級人肉市場相當常見,突然發現自己身高未達標、公司也沒名氣,連煩惱的機會都沒有。至於竹科到底有多綠,或者哪一方有多現實,也許從一開始就知道了。

在某個周末,家父打電話跟我說朋友的女兒不排斥認識,要我在台北約一約聊一聊。同樣是花蓮教育界的家庭背景,就不用煩惱價值觀差異。仔細思考一番,便覺得有所道理。要在茫茫人海中,找到那麼一個類型的女性是多麼困難。

聊了一陣子後,雙方父母的催促下,才單獨約出去餐廳吃個飯、見個面,如同相同帳號的命名規則,知道對方生日並不是難事。找餐廳的時候,店員順口提及了有沒有要慶生,想了想時間點也對,但對於第一次單獨約人吃飯就走這一險棋,真不知道那天的我在想什麼。

陸陸續續聊到了年底,覺得可能是時間點不對,對方剛入職為了試用期而努力,而自己也因為公司繁重的工作沒什麼特別心思,普通的聊聊就得顧慮太多項目,並不是像大部分的主流商品,能夠樂觀、開放、愛旅遊、健身的全能人才,的確沒什麼吸引力讓對方主動邀約。

時間久了,主動發話就沒了主題,敗給無趣的自己。

參與婚宴

到了這個年紀後,作為賓客被邀約去婚宴的機會更多,不再是被爸媽帶去的情況。不經感嘆,原來我還有朋友會要我去參加,明明那個時候的我是多麼地頹廢,為了挨過那個階段的障礙,表現得一點也不成熟。

畢業後的幾年裡,參加了學弟、大學同學、實驗室同學、及高中同學的婚宴,有的還特別從台北搭車跑到台南參加。每一次婚宴都像久違朋友的大型聚會,大家都不計前嫌地聊著近況,商討著哪裡有更好的工作,自己人生的下一階段目標是什麼。

看到不少認識的朋友帶著男伴、女伴參加,有點後悔自己在讀書階段都沒好好留意身邊的人,現在開始要找個隊友,卻敗給時間成本的疑慮。不時看到各大公司的花邊新聞,就會煩惱要怎麼選擇。

年底飛到台南參與,時至今日,高中畢業已十年,整班三十人,卻有三分之二的出席率。當醫生的都可以坐一桌,有的還在國外發展,有的還在進修博士,形形色色的發展都有,高中班級導師還特地從花蓮趕來,可以說是老同學聚會。如果有機會辦,也能這麼風光嗎?

下個階段

疫情剛開始時,受到親戚的邀約跑去看房子,那時候一個人做不了什麼決定,身上也沒足夠的錢,對於買賣也沒足夠的社會經驗。有幾次在同學聚會中談論到,他自己貸款買了上千萬的透天,跟準新娘一起共同努力的感覺就不一樣。有了目標,眼界與嘗試的機會也有所不同。

時隔一年,大部分時間都在家工作的我,遇上了室友要飛往美國讀書的情況,不經得深思台北省錢合租的生活,充滿了多不穩定的因素。

Read More +