注:本文所示代碼均爲僞代碼javascript
假設咱們要編寫一個函數function parse_data(String Raw):Object[]
用來解析一個列表數據到應用程序之中。在二十一世紀的今天,咱們會很天然的聯想到使用JSON
來完成這項需求。由於咱們能夠脫口而出JSON
全部的優勢:java
1. 兼容性良好。 2. 開源支持力度好。 3. 人類閱讀友好。
所以,咱們能夠很快速的獲得一個實現版本。sql
function parse_data(String raw): Object[] { let lst: Object[]; lst = JSON.parse_into<Object[]>)(raw) return lst }
在至關多的場景裏面,以上的實現都堪稱完美。假設咱們將程序的運行條件限定一下:性能優化
1. raw String 可能超過 1GB 2. 物理內存只有256MB
在以上場景的約束下,第一個實現已經沒法正常工做了。由於JSON格式必須將其完整的載入內存才能夠進行解析,從約束條件來看,raw string 大小已經遠遠超過了內存限制;同時,將全部數據都解析到內存也是很大的內存開銷。從這個分析來看,咱們得出了一個結論:第一個版本沒法在內存條件苛刻的狀況下工做,所以咱們須要進行優化。多線程
那麼,咱們的優化的思考點應該是怎麼樣的嘞?app
咱們須要先回過頭了看問題,第一個實現的問題是內存佔用過大引發的。那麼,咱們就須要一個方案來減小內存的佔用。 減小運行時內存佔用 -- 這個就是咱們本次優化的哲學指導。函數
咱們已經有了基本的哲學指導思想,那麼咱們開始進入到具體問題具體分析的階段去尋找解決方案。咱們再深刻的思考第一個實現爲何會形成內存佔用高:性能
1. 須要所有載入數據 2. 須要所有將解析結果存到內存
也就是說,咱們的新方案只須要解決上面兩個問題,也就完成了目標。學習
通過一番搜索與學習,咱們發現可使用Streaming友好的數據格式(Msgpack,CSV)來做爲raw string,同時將數據使用sqlite來存儲解析後的結果。優化
此時此刻,咱們就獲得了第二個實現版本。
function parse_data(String filePath): Object[] { let db_conn = Sqlite.open("./cache.db") CsvReader .open(filePath) .each((line)=>{ db_conn.insert_into(convert_line_to_object(line)) }) }
這個實現經過將raw string 放入磁盤,同時利用csv行間隔離的特性。經過流式的方式將數據遷移到本地db中。對於物理內存的需求基本是趨近於O(1)的。從而解決了咱們上面提出的兩個問題。
那麼,這個版本就完美了嗎?
從代碼邏輯上來看,這個版本對於數據量的限制從內存轉移到了磁盤,在實際過程當中能夠認爲解決了數據量代碼沒法工做的問題。可是它仍然存在一個問題 -- 磁盤IO過於頻繁,磁盤IO表如今兩個方面:
1. 讀取csv 2. 寫入sqlite
那麼,咱們是否有可能再次優化嘞?
function parse_data(String filePath): Object[] { let file_lst = CsvUtil.split_file_by_lines(filePath,1000) // per csv file 1000 line let db_conn = Sqlite.open("./cache.db") foreach file_lst as file: Thread.run(()=>{ let lst: Object[]; CsvReader .open(file) .each((line)=>{ lst.append(convert_line_to_object(line)) }) db_conn.batch_insert(lst); }) end wait_all_thread_done() }
基於咱們提出的疑問,咱們編寫了這個版本。這個版本提出了批處理的概念。也就是將一個任務拆分紅數個互相獨立的子任務,同時引入了多線程來發揮多核優點。
到此,咱們就實現了一個多核加速、數據量無上限(假設磁盤無上限)的數據解析器
。固然,同時代碼複雜度也指數級上。
本文主要目的是利用了一個實際案例,來討論咱們作性能優化的思辨過程:
1. 在優化以前,發現主要矛盾 2. 根據主要矛盾的來推演咱們須要的解決方案 3. 尋找解決方案,而且肯定該解決方案能夠解決問題 4. 根據須要看是否須要繼續優化,若是優化就回到#1。