記得在上個月,微博上有一則熱議得新聞:小學數學老師布置作業,要求「數一億粒公尺」。
網友大多數是以吐槽的態度去看待這件事,也有人指出能用估算的方法,這是一道考察發散思維的題目。
一開始我也覺得這個題目很荒唐,似乎是不可能完成的任務。但這個細細想來值得玩味,我在思考乙個問題:如果從計算機的角度去看,如何才能最快速地數一億粒公尺呢?
首先我們先將問題簡單地抽象一下:
抽象過程
作為有煮飯經驗的我來說,公尺中是存在一些雜質的,所以數公尺應該不僅僅是單純的數數,其中還有乙個判斷是公尺還是雜質的過程。
那麼可以將其視作乙個長度為l的陣列(l大於一億),這個陣列是隨機生成的,但是滿足陣列的每個元素是乙個整型型別的數字(0或1)。約定:元素如果為1,則視作有效的「公尺」;如果為0,則視作無效的「雜質」。
為了更快地完成計算,並行的效率應該是比序列來得高。
那麼我們將乙個人視作乙個工作執行緒,全家一起數公尺的情景可以視作併發情況。
有了以上的鋪墊,接下來就是最核心的問題,如何才能最快地數一億粒公尺。我不妨假設以下的幾種情景:
小季說:「媽媽,今天老師布置了一項作業,要數一億粒公尺。」
媽媽:「找你爸去。」
爸爸:「?」於是爸爸乙個人開始數公尺,開啟乙個迴圈,遍歷整個陣列進行計算。
以下是單執行緒執行的**。
首先定義乙個計算介面:
public inte***ce counter
複製**
爸爸迴圈數公尺:
public class fathercounter implements counter
return total;
}}複製**
主函式:
public static void main(string args)
複製**
最後的運算結果:
消耗時間(毫秒):190
複製**
我執行了多次,最後的消耗時間都在190ms左右。這個單執行緒迴圈計算平平無奇,沒有什麼值得深究的地方。由於大量的計算機資源都在閒置,我猜測,這肯定不是最優的解法。
執行緒池executorservice
爸爸乙個人數了一會,覺得自己乙個人數公尺實在是太慢了,家裡有這麼多人,為什麼不大家一起分攤一點任務呢?每個人數一部分,最後再合併。
於是小季全家總動員,一起來完成作業。
除去三大姑八大姨,現在到場的有爸爸、媽媽、哥哥、姐姐、爺爺、奶奶、外公、外婆八位主要家庭成員(8個cpu的計算機)。
小季說:既然要數1億粒公尺,那麼就你們每人數12500000粒公尺,然後再合併一起吧!
爸爸說:崽子,別想偷懶,我剛剛數過了,現在換你去,我來給你們分配任務。(主線程)大家說幹就幹,各自埋頭工作起來。
以下是使用executorservice方式的**:
還是同乙個介面:
public inte***ce counter
複製**
建立乙個新的實現類:
public class familycounter implements counter
private static class counterricetask implements callable
@override
public long call() throws exception
return total;
}} @override
public long count(double ricearray)
for (futurej : results) catch (interruptedexception e) catch (executionexception ignore)
}return total;
}}複製**
主函式依舊是原來的配方:
public static void main(string args)
複製**
最終輸出:
消耗時間(毫秒):46
複製**
我執行了多次,結果都在46ms左右,說明這個結果具有一般性。那麼有乙個問題來了,既然乙個人數公尺花費了190ms,那麼照理來說8個人同時工作,最終應該只需要190/8=23ms呀,為什麼結果是46ms?
消耗時間(毫秒):62
複製**
可見執行緒之前的切換消耗了一定的資源,所以很多情況下並非「人多好辦事」,人多所帶來的團隊協調等問題,可能會降低整個團隊的工作效率。
執行緒池forkjoinpool
在之前大家埋頭數公尺的過程中,爸爸作為任務的分配者,也在觀察著大家。
他發現,爺爺奶奶由於年紀大了,數公尺速度完全比不上眼疾手快的哥哥姐姐。哥哥姐姐完成自己的任務就出去玩了,最後只剩爺爺奶奶還在工作。年輕人居然不為老人分憂,成何體統!
小季(內心os):爸爸,好像只有你一直在玩。於是,爸爸在想能不能有乙個演算法,當執行緒池中的某個執行緒完成自己工作佇列中的任務後,並不是直接掛起,而是能幫助其他執行緒。
有了,這不就是work-stealing演算法嗎?爸爸決定試試forkjoinpool。
如何劃分子任務呢?fork/pool採用遞迴的形式,先將整個陣列一分為二,分為left和right,然後對left和right進行相同的操作,直到陣列的長度到達乙個我們設定的閾值(這個閾值十分重要,可以影響程式的效率,假設為1000),然後對這個長度的陣列進行計算,返回計算結果。上層的任務收到下層任務完成的訊息後,開始執行,以此傳遞,直到任務全部完成。
以下是使用forkjoinpool方式的**:
public class togethercounter implements counter
private static class counterricetask extends recursivetask
@override
protected long compute
() return total;
}else
}} @override
public long count(double ricearray)
}複製**
當我把閾值設定在7000-8000的時候,計算時間縮短到了驚人的15ms,效率又提公升了3倍之多!
消耗時間(毫秒):15
複製**
得到這個結果,爸爸十分滿意。此時小季卻疑惑了,同樣是並行,為什麼效率相差這麼大呢?
爸爸摸著小季的頭,說道:這個還是需要看具體的場景。並不是所有情況下,forkjoinpool都比executorservice出色。
forkjoinpool主要使用了分治法的思想。它有兩個最大的特點:
爸爸喝了口咖啡,繼續說道:在jdk8中,forkjoinpool新增了乙個通用執行緒池,這個執行緒池用來處理那些沒有被顯式提交到任何執行緒池的任務。這也是為什麼arrays.sort()快排速度非常快的原因,因為引入了自動並行化(automatic parallelization)。
小季若有所思:爸爸,我完全聽不懂啊,我還是只是個四年級的孩子。
只有計算機專業出身才能做程式設計師嗎?
對於這個問題我只能回答 計算機專業的畢業生大多數會以程式設計師作為擇業目標,程式設計師也不一定非得是計算機專業畢業的。要知道作為國內網際網路公司三巨頭的阿里巴巴,創辦人正是絲毫不懂程式設計的馬雲。馬雲曾經是一名英語教師,在那個資訊科技十分閉塞的年代,馬雲是第乙個因為懂英文而受益的人。也是英文讓他認識...
計算機與數學的關係
說明 這是 別人的一篇文章,可能沒有什麼實際的指導性意義,但我個人認為,計算機學習的不僅僅是應用,還有思想 一種數學思維 管理學思維,數學是學好 學精計算機技術的前提,無數圖靈獎獲得者都是數學界的專家足以證明這一點。為此,我特定收集了一些與計算機有關的數學資料,希望對大家有所幫助。電腦科學和數學的關...
計算機與數學的關係
電腦科學和數學的關係有點奇怪。二三十年以前,電腦科學基本上還是數學的乙個分支。而現在,電腦科學擁有廣泛的研究領域和眾多的研究人員,在很多方面反過來推動數學發展,從某種意義上可以說是孩子長得比媽媽還高了。但不管怎麼樣,這個孩子身上始終流著母親的血液。這血液是the mathematical under...