這篇主要記錄一下學習陳碩同學的對下面這道題的算法思想與代碼。
題目是這樣的:
有10個文件,每個文件1G,每個文件的每行存放的都是用戶的query(請自己隨機產生),每個文件的query都可能重復。要求你按照query的頻度排序。
(當然,這里的重點是大文件,所以10個1G的文件,或者1個10G的文件,原理都是一樣的)
陳碩的代碼在這里:
這是一段非常漂亮的代碼,解法與代碼都非常值得一看。
【解法】
基本步驟就是不斷讀入文件,並做初步統計,到了某個內存的極限時寫出文件,寫的方式是按query的哈希值分配到10個不同的文件中,直到讀完所有文件內容,然后把這10個文件中的query按count排序,並10路歸並排序出最后結果。
shuffle
從命令行傳入輸入文件,逐行讀入,並存放在一個hashmap中,邊讀邊統計<query, count>,到map的size到達指定size時(10*1000*1000, 主要是考慮內存容量),把這個hashmap的內容先寫出去,寫到10個文件的第hash(query) % 10個中去,這保證了相同的query肯定在同一個文件中。 這樣,直到把文件讀完。所以如果輸入文件總大小為10G的話,每個文件大小為 <1G (因為相同的query並合並了),可以進行單文件內存內處理。注意此時雖然相同的query在同一文件中,他們可能是分布在好幾個地方的,如:
query1 10
query2 5
query3 3
query1 3
query4 3
query 2 7
reduce
把每個文件中相同query合並,並將query按count排序。
merge
10個有序的文件,通過歸並排序得到最后的結果。歸並的方式是通過一個10個元素的堆,相比於兩兩迭代歸並排序,這大大減少了讀文件的時間。
【運行】
該程序只在linux下運行,並需要boost,ubunut下,先安裝boost:
apt-get install libboost-dev
然后編譯,該程序用到了c++ 0x的feature,所以需要-std=c++0x:
g++ sort.cpp -o sort -std=c++0x
在運行前,需要准備輸入數據,這里用lua隨機產生:(https://gist.github.com/4045503)
-- updated version, use a table thus no gc involved local file = io.open("file.txt", "w") local t = {} for i = 1, 500000000 do local n = i % math.random(10000) local str = string.format("This is a number %d\n", n) table.insert(t, str) if i % 10000 == 0 then file:write(table.concat(t))
t = {} end end
好,開始運行:
sort file.txt
結果如下:
$ time sort file.txt
processing file.txt
shuffling done
reading shard-00000-of-00010
writing count-00000-of-00010
reading shard-00001-of-00010
writing count-00001-of-00010
reading shard-00002-of-00010
writing count-00002-of-00010
reading shard-00003-of-00010
writing count-00003-of-00010
reading shard-00004-of-00010
writing count-00004-of-00010
reading shard-00005-of-00010
writing count-00005-of-00010
reading shard-00006-of-00010
writing count-00006-of-00010
reading shard-00007-of-00010
writing count-00007-of-00010
reading shard-00008-of-00010
writing count-00008-of-00010
reading shard-00009-of-00010
writing count-00009-of-00010
reducing done
merging donereal 19m18.805s
user 14m20.726s
sys 1m37.758s
在我的32位Ubuntu11.10虛擬機上, 分配了1G內存,1個2.5G的CPU core, 處理一個15G的文件,花了19m分鍾。
【學習】
- 把query按哈希值分配到不同的文件,保證想通過query在同一個文件中,漂亮
- 10路歸並排序,用一個最大(小)堆來做,減少了文件讀寫,漂亮
- LocalSink, Shuffler, Source等很小的類來封裝、解耦一些特別的的任務,結構十分漂亮
- 一些我不熟悉的知識:
- __gnu_cxx::__sso_string, gnu short string optimization, 這里有跟更多的說明
- boost::function , boost::bind
- 使用map的[] operator時,插入數據根據默認構造函數初始化,對於int默認則是為0
- C++ 0x的for each:for (auto kv : queries)
- boost::noncopyable:不能被copy的類從此繼承
- std::hash<string>(): 返回一個針對string的hash functor
- boost::ptr_vector:boost針對每個container都一共了一個ptr的版本,這個比單純的使用vector<shared_ptr<T>>要更高效
- unlink: delete的一個文件
- std::unordered_map<string, int64_t> queries(read_shard(i, nbuckets)):使用了move sematic,不然效率會很低
- std::pair定義了 < operator,先比較第一個元素