性能優化:空間換時間

問題背景

       在程序開發過程當中,咱們對於數據的處理,會有一些校驗。 
       校驗分爲兩種:簡單校驗複雜校驗

       對於一些簡單的校驗,如用戶是否存在,密碼是否正確等等。這種校驗,能夠說幾乎不耗時的。因此也不必在這裏作優化。 
       對於複雜的校驗,須要進行聯合查詢,經過查詢不少次以後,才能夠得出 數據的正確性與否。固然這種校驗執行會很慢。

       對於程序開發來講,時間複雜度和空間複雜度是能夠相互轉化的。說通俗一點,就是:對於執行的慢的程序,能夠經過消耗內存(即構造新的數據結構)來進行優化。而消耗內存的程序,也能夠多消耗時間來下降內存的消耗。 
       前者使用的是最多的。不多有人會爲了節省內存而浪費時間。

       感興趣的同窗,請仔細看完這個例子。看如何是如何消耗內存來提升性能的。若是有不正確的地方,還請指出來。

先舉例一個場景。來分別 看一下  正常思路的處理方法和優化事後的處理方法:

       好比說給學生 排課。 學生 和 課程 是一個多對多的關係。 




       按照正常的邏輯 應該有一個關聯表來維護 二者之間的關係。 



如今,添加一個約束條件 用於 校驗。如:張三 上學期 學過 的課程,在排課的時候不該該再排這種課程。 



 因此須要出現一個約束表(即:歷史成績表)。



即:學生選課表,須要 學生成績表做爲約束。

處理方式對比

方案一:正常的處理方式:

       當一個學生進行再次選課的時候。須要查詢學生選課表看是否已經存在。

即有以下校驗:


//查詢 學生code和課程code分別爲  A 和 B的數據是否存在

//list集合中存放  學生選課記錄所有的數據
List<StudentRecordEntity> ListStudentRecord=service.findAll();   
//查詢數據,看是否已經存在
StudentRecordEntity enSr=ListStudentRecord.find(s=>s.學生Code==A && s.課程Code==B);
If(enSr==null){
    //學生沒有選該課程
    //....
}else{
    //學生已經選過該課程
    //....
}



對於上面這種代碼的寫法,很是的簡練。並且也很是易懂。 

       首先,假設有5000個學生,100門課程。那麼對於學生選課的數據集中,數據量將是5000*100.數據量會是十萬級別的數量級。

       在十萬條數據中,查詢 學生=A 課程=B的 一條記錄。執行的效率會很低。由於find方法的查詢也就是where 查詢,即經過遍歷數據集合 來查找。 

       因此,使用上面的代碼。在數據量逐漸增加的過程當中,程序的執行效率會大幅度降低。 
       (ps:數據量增加,在該例子中並不太適合。例子可能不太恰當。總之,大概就是這個意思。)


方案二:使用內存進行優化效率:

       這種作法,須要消耗內存。或者說把校驗的工做向前作(數據的初始化,在部署系統的過程當中進行)。即:在頁面加載的時候數據只調用提供的public方法進行校驗。


//學生Code  到   數組索引
Private Dictionary<string,int> _DicStudentCodeToArrayIndex;
//課程Code  到   數據索引
Private Dictionary<string,int> _DicCourseCodeToArrayIndex;

//全部學生
List<StudentEntity> ListStudent=service.findAllStudent();
//全部課程
List<CourseEntity> ListCourse=service.findAllCourse();
//全部 學生選課記錄
List<StudentCourseEntity> ListStudentRecord=service.finAll();

Private int[,] _ConnStudentRecord=new int[ListStudent.count,ListCourse.count];

//構造 學生、課程的  數組 用於快速查找字典索引
Private void GenerateDic(){
    For(int i=0;i<ListStudent.Count;i++)
        _DicStudentCodeToArrayIndex.Add(ListStudent[i].code,i)
    }
    For(int i=0;i<ListCourse.Count;i++){
        _DicCourseCodeToArrayIndex.Add(ListCourse[i].code,i)
    }
}

//構造學生選課 匹配的 二維數組。 1表示 學生已選該課程
Private void GenerateArray(){

    Foreach(StudentRecordEntity sre in ListStudentRecord){
        Int x=_DicStudentCodeToArrayIndex[sre.學生Code];
        Int y=DicCourseCodeToArrayIndex[sre.課程Code];
        ConnStudentRecord[x,y]=1;
    }
}

//對外公開的方法:根據學生Code 和課程Code  查詢 選課記錄是否存在
/// <returns>返回1 表示存在。返回0表示不存在</returns>
Public void VerifyRecordByStudentCodeAndCourseCode(String pStudentCode,String pCourseCode){
    Int x=_DicStudentCodeToArrayIndex[pStudentCode];
    Int y=_DicCourseCodeToArrayIndex[pCourseCode];

    Return ConnStudentRecord[x,y];
}

性能分析

分析一下第二種方案的表象。 
       一、方法不少。 
       二、使用的變量不少。

       首先要說一下。該優化的目的,是提升 學生在選課的時候,所出現的卡頓現象(校驗數據量大)。

分別對以上兩種方案進行分析:

       假設學生爲N,課程爲M

第一種方案: 
       時間複雜度很容易計算 第一種方案最小爲O(NM) 
第二種方案: 
       一、代碼多。可是給用戶提供的只有一個VerifyRecordByStudentCodeAndCourseCode方法。 
       二、變量多,由於該方案就是要使用內存提升效率的。 
       這個方法執行流程:一、在Dictionary中使用Code找Index 二、使用Index查詢數組。

       第一步中,Dictionary中查詢是使用的Hash查找算法。時間複雜度爲O(lgN) 時間比較快。第二步,時間複雜度爲O(1),由於數組 是連續的 使用索引 會直接查找對應的地址。 
       因此,使用第二種方案進行校驗,第二種方案時間複雜度爲O(lgN+lgM) 

小結

       經過上面的分析,能夠看出,內存的付出是能夠提升程序的執行效率的。以上只是一個例子,優化的好壞取決於使用的數據結構。