一段阿里筆試程式碼的(瞎)分析
本文地址
前言
我是菜雞,如有不對的地方煩請指正。
起始
上週(好像是上週)的時候作業系統的老師丟擲了一個問題留給我們:
對以下程式在一臺主流配置 的PC上,呼叫f(36)所需要 的時間大概是多少?請給出時間估算的依據並對程式的執行情況進行詳細的解析說明。
int f(int x) {
int s = 0;
while (x++ > 0) s += f(x);
return std::max(s, 1);
}
第一反應
顯然在棧資源無窮且使用超算的情況下,這個遞迴的壽命會比銀河系的壽命(140億年,我查的)還要長,因為總規模為。 但題目要求考慮實際情況,所以考慮一些別的東西。
注
以下所有(瞎)分析建立在忽略編譯優化及O2
優化的情形下。
硬體資源
拋開四萬塊起步的iMac Pro(128GB記憶體和18核超執行緒Xeon),現在主流PC配置普遍是16GB記憶體和6核12執行緒的i7 8700k(暫不考慮剛釋出的9700k,因為還沒有流片)或8核16執行緒的Ryzen 7 2700k。
但是!其實用什麼CPU都無所謂,因為單純地執行這個程式實際取決於記憶體和硬碟的一些引數和CPU的單核運算效能,而且由於作業系統的存在,只要不是上個世紀的電腦,實際執行中都不會對這個程式的執行時間產生多大的限制。
一般來說,在演算法競賽中普遍認為CPU的時間複雜度上界為,實際上要低於這個,大概是極限了(我在Codeforces
和NowCoder
測試了的快排以得到這個資料)。
程式碼(瞎)分析
對於每一次呼叫,它都會重複呼叫自己(次),試圖畫一下遞迴樹:
其中:每個節點上的數字x
代表呼叫一次f(x)
,可見,從樹根開始,第一層有個節點,第二層有個節點,第三層有個節點,其真值為,第四層的節點總數為,這個式子我不太會估值,隊友說很easy(Orz)。
但是一層一層的算節點個數並不符合實際情形,雖然這樣能比較容易地計算樹的實際規模,但是這不滿足深搜樹的實際增廣時的行為。
對於深搜樹來說,一條樹鏈的最大長度等於樹的高度,同時也是深度,也就是說在記憶體中最多隻能同時存在個這樣的遞迴子程式,我們來粗略計算一下它需要的記憶體資源。
取變數和的記憶體佔用(8位元組)作為單個子程式的記憶體佔用,那麼實際佔用的資源約為,也就是說需要16GB
的棧大小。
但是!Linux
下的棧大小大約為8MB
(比8MB
小一點),垃圾Windows
更是少得可憐,所以到這裡沒有繼續(瞎)分析下去的必要了。
結論
這個遞迴程式在爆棧之後就會立刻結束,所以說在寫入大概次之後程式就會結束,忽略遞迴的呼叫時間理論上應該在秒左右,但因為遞迴呼叫的存在,實際情況要大一些,但憑經驗判斷理應在秒以內,這裡不再做深入的探究。
能否讓這個程式健康地執行
答案是可以的,可以用全域性棧來模擬遞迴呼叫,這樣實體記憶體有多大程式本身就可以用到多大的記憶體,此時如果你的記憶體足夠大,則取決於CPU的單核運算效能,如果記憶體小於16GB
,那麼會發生記憶體交換,由於木桶效應的存在,此時程式的運算速度就會收到硬碟讀寫速度的制約。
樹的規模證明
首先,可以將上文中提到的深搜樹剪為一顆左偏深搜樹: 易知,這棵樹的整體規模為,推廣到原樹上,每過一層就會多一層,顯然,層這樣的逼近於。
證畢。