Shiny People 感謝

背景原因

公司有一套 Shiny 機制,可以在特別日期或事件後,來表揚合作不錯的長官同事。有時候表揚可以帶鼓勵金的,當然這筆錢不會由您支付,而是由部門的某一個錢包裡出,理所當然得不可能存在同部門互相洗錢的行為,仍然遵循某一種流程。

詳細信件內容有點不同了,就當作有朝一日會在網路上搜尋到吧。

任職周年慶祝

Software Architect

To Jeffrey Morehouse:

I really appreciate your great art, which facilitates high productivity compared to other products. In these years, give the opportunity to develop my improvement thought in core. During coding experiment, your resource and past experience help me a lots. Although we’re working on different ways these years, I’m looking forward to having technical discussion with you one day. Congratulate to Techholic!

感謝您轉交下來的偉大傑作,讓我們產品相較於其他產品
擁有非常高的生產力、極低的開發週期。同時在這幾年間,
給予我機會去實踐自己的想法。並在開發過程中,給不少
資源與過去經驗參考,這些都讓我學了很多。

雖然這幾年間沒有共事,期待未來可以分享這些技術細節。
為我們的技術狂熱慶祝!

Manager

To Thunder Lay:

I’m glad to be the early employee in your part. The unfailingly patient leader is very important to me in first job. Even though we’re good at different profession, we learned from each other to complish great big tasks these years. That is very good experience, and rare in many kinds of job career.

很高興地成為早期的部門成員,對我第一份工作而言,
您的耐心指導成為很大的助力,雖擅長不同領域的專長,
在這幾年間,取長補短地完成了許多重大任務,這真的
是在其他職業生涯中可未必能見到的非常好的經驗。

結語

寫完英文,看著自己的譯文,赫然發現我的中文死去,找些朋友提前問個意見後,只得到了一致的答覆「看不懂」。在沒有前因後果,不具備相同處理的條件下,看起來可能是誰要離職了吧。

Read More +

Company Ghost Story 公司鬼故事 13

Java

Default Constructor Trap

1
2
3
4
5
6
class Point {
Point() {
throw new UnsupportedOperationException(); // why?
}
Point(int x, int y) {...}
}

一開始不宣告就好了,當有自定義的建構子,預設建構子自然無法被使用,沒必要設一個陷阱讓別人踩。

Feature and Backward Compatibility

1
2
3
4
5
6
class Engine {
private String mFeature = ""; // Why not null ?
}
class DbObj {
private String mFeature = ""; // Why not null ?
}

空與空字串的意義不同,預設為空字串 ("") 的狀況並不多見,為空 (null) 在序列化和反序列化時的差異就很大了。

Fancy Check-and-Cast

1
2
3
Shape shp;
if (shp.getUserName().equals("circle")) // wait, where is your `instanceof`
return ((Circle) shp).getBound();

Call-By-Value

1
2
3
4
5
void readColRow(int col, int row) {
Pair p = readPair();
col = p.first;
row = p.second;
}

Java 沒有 reference type 的這操作。

Slow Feature

1
2
3
void exec() {
for (;;) {} // where is throw new UnsupportedOperationException();
}

我可不想跟客戶說「你再等等,這功能只是比較慢」的謊言。

Read More +

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

「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 +