ARTS 是陳浩(網名左耳朵耗子)在極客時間專欄裏發起的一個活動,目的是經過分享的方式來堅持學習。node
每人每週寫一個 ARTS:Algorithm 是一道算法題,Review 是讀一篇英文文章,Technique/Tips 是分享一個小技術,Share 是分享一個觀點。
本週你將看到:golang
本週的算法題是一道鏈表和歸併排序結合的題目:23. Merge k Sorted Lists.算法
這道題目的解法不陌生,對於用 Go 來解這道題的我來講,這道題的難點反而是在實現上。Go 中的 container/heap
我以前是不瞭解的,幸好在我決定本身實現一個堆以前嘗試搜索了一下 Go 是否有相似的工具。不過話說回來,container/heap
這個工具也着實過於簡單。只幫你實現堆化(heapify),其餘的堆內實際數據類型須要你本身定義,同時類型須要支持 Less()
方法,若是不支持的話須要你本身定義。此外你還要實現上述類型的 Pop()
和 Push()
方法對底層數據的具體操做方式。其餘須要注意的地方我都有已經寫在註釋中了。編程
func mergeKLists(lists []*ListNode) *ListNode { if len(lists) == 0 { return nil } var fhead = &ListNode{} h := &IntHeap{} heap.Init(h) for i := range lists { if lists[i] == nil { continue } heap.Push(h, node{Val: lists[i].Val, ListIdx: i}) lists[i] = lists[i].Next } curr := fhead // container/heap 會自動幫你作 heapify // 每次從小頂堆 pop 以後要從原來的 list 中再取出一個 node // 若是原來的 list 爲空就繼續 pop // 這也就是爲何不直接存 ListNode.Val 到 heap 裏 // 爲了保存當前從堆 pop 出來的值來自哪一個 list 專門聲明瞭 node 類型 // 另外,可能你會對爲何必定要從 pop 出來的值原來的 list 再取一個 node // 緣由在於,本地的 merge 邏輯就是隨時都在堆裏放當前全部 list 的最小值 // 每次都從原 list 取一個值放進堆裏,這個值被 push 進去以後可能不會成爲堆頂 // 可是成爲一旦原 list 的下一個值可能成爲堆頂或者可能在其被 push 進堆以前成爲堆頂 // 就會形成最終 merge 排序的亂序 // 因此最穩健和簡單的作法就是每次都從原 list 找下一個值 push 到堆裏 for h.Len() > 0 { n := heap.Pop(h).(node) curr.Next = &ListNode{Val: n.Val} if lists[n.ListIdx] != nil { heap.Push(h, node{ Val: lists[n.ListIdx].Val, ListIdx: n.ListIdx}) lists[n.ListIdx] = lists[n.ListIdx].Next } curr = curr.Next } return fhead.Next } type node struct { Val int ListIdx int } type IntHeap []node func (h IntHeap) Len() int { return len(h) } // Less 遞增表示構成小頂堆 func (h IntHeap) Less(i, j int) bool { return h[i].Val < h[j].Val } func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(node)) } func (h *IntHeap) Pop() interface{} { ret := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return ret }
本週推薦的文章是 Go 官網的一篇性能調優的科普文章:Profiling Go Programs.api
這篇文章實際上是對下面這篇論文Loop Recognition in C++/Java/Go/Scala的一個官方迴應,由於論文裏的幾種語言性能的測試結果對 Golang 很不「友好」。總之從結果來看 Go 在上述基準測試中的表現都不太好,Go 官方表示並「不是咱們性能不太好,而是大家不互用!」緩存
The Go program presented in that paper runs quite slowly, making it an excellent opportunity to demonstrate how to use Go's profiling tools to take a slow program and make it faster.
怎麼樣,是否是感受似曾相識?app
因而官方把怎麼使用 pprof
工具開始,再到具體優化 Go 代碼的 CPU 佔用和內存使用效率,全在這篇文章裏介紹了一遍。固然,對於具體的優化內容不可能面面俱到。文中主要是對某些數據類型是不用不當(好比將 map 改爲 slice)下降 CPU 的使用率,對某些頻繁建立和刪除的對象作了本地緩存來下降內存申請和垃圾回收對 CPU 的消耗。工具
上面的兩項措施從基準結果來看效果顯著,而後做者不忘嘲諷一下以前那篇論文。oop
Having a garbage-collected language doesn't mean you can ignore memory allocation issues. In this case, a simple solution is to introduce a cache so that each call to FindLoops reuses the previous call's storage when possible. (In fact, in Hundt's paper, he explains that the Java program needed just this change to get anything like reasonable performance, but he did not make the same change in the other garbage-collected implementations.)
最後,一頓操做以後,做者給出了優化的結果:比論文中的結論至少能夠快 6 倍到 11 倍。同時,做者強調「還有」能夠優化的地方存在。可是,後續的優化操做在使用的工具和方法上,與上述優化過程當中使用過的工具和方法沒有任何區別。性能
There's more we can do to clean up the program and make it faster, but none of it requires profiling techniques that we haven't already shown.
最後的最後,做者還要補充一句 Go 和 C++ 能夠在「某種程度上」不相上下。
Benchmarks are only as good as the programs they measure. We used go tool pprof to study an inefficient Go program and then to improve its performance by an order of magnitude and to reduce its memory usage by a factor of 3.7. A subsequent comparison with an equivalently optimized C++ program shows that Go can be competitive with C++ when programmers are careful about how much garbage is generated by inner loops
本週很想找到一個變成技巧,但我仍是失敗了。哭幹了眼淚。
週五晚上和一個老同窗聊到了一些在各自不一樣的公司項目管理模式的經歷。對於我目前的工做來講,項目的進度徹底不須要我去考慮,由於有項目經歷在負責推薦整個項目。我只須要負責我本身的部分,保證能按時完成。若是有對接的其餘部門或者團隊的開發進度比預期更慢的話,我都會讓項目經理去解決。而個人同窗所在的公司在這方面彷佛分工並不嚴格,致使個人這位同窗默默作了不少催促對接團隊項目進度的工做,甚至是爲對方的團隊進行了一些友情援助式的開發工做。
然而,項目到最後仍是未能定期交付。對方的團隊領導反而以爲我這位同窗工做內容不夠飽和,一直是「嘴強王者」,而事實上他已經提早半個月完成了本身的開發工做。這種事情在我這裏是不可能發生了,我首先就不可能跑去別的團隊催促他們的工做進度。由於那不是個人職責,再者由於我不瞭解對方的項目排期和優先級等等等,我更毫不可能去插手其餘團隊。
說實話聽到我這個同窗的經歷,我是震驚的。首先我沒法理解他爲何會插手其餘團隊的工做,再次我也沒法理解對方團隊這種乘人之危的行爲。這些原本應該由項目經理完成的工做,若是平時的開發順利的話,是很難注意到的。可一旦發生了某些問題致使項目須要人爲推動,那麼項目經理的重要性就會凸顯出來。
因此在平時的開發中,不要總以爲項目經理就是催你我幹活的,他們多數時候都是在幫你解決不少你絕對不想本身解決的問題。