從一個實際案例來討論咱們應該如何作性能優化

如何優化一個數據解析器?

注:本文所示代碼均爲僞代碼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。
相關文章
相關標籤/搜索