从一个实际案例来讨论咱们应该如何作性能优化

如何优化一个数据解析器?

注:本文所示代码均为伪代码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。
相关文章
相关标签/搜索