2013/09/08

動態地讓Array Size增長 - Part 3

在前兩篇文章〈動態地讓Array Size增長 - Part 1〉與〈動態地讓Array Size增長 - Part 2〉中,介紹了如果不能夠事先知道陣列(array)大小,可以使用Shift Register + Build Array 來動態地讓陣列長大。這個技巧雖然簡潔易用,但有一個缺點:記憶體使用效率不佳
圖一:動態成長手法

以圖一為例,當我們使用Build Array元件將一個長度為5的1D Array與獨立元素結合成一個長度為6的1D Array時,LabVIEW除了原本長度為5的1D Array外,會需要再向作業系統請求一個新的記憶體位置,來存放長度為6的1D Array。並且把新的這個Array存入Shift Register內,並將舊的Array刪除;若使用動態成長手法讓Build Array執行了一萬次,那麼請求新記憶體位置與刪除舊記憶體位置的動作也要執行一萬次。


這光用聽的就知道很沒有效率!不僅如此,當陣列成長到一定程度時,記憶體內有可能沒有足夠長度的連續區塊,因此作業系統需要搬運既有的資料清出位置給新的陣列,會進一步地拖慢程式執行效率。這在操作大型資料時,有可能成為拖慢執行效率的兇手。解決方式,就是預先配置一個夠大的陣列,待資料寫入完成後,再把陣列內多餘的元素砍掉,縮減到正確的長度。

以下我們仍然以〈動態地讓Array Size增長 - Part 1〉之中的範例來說明(見圖二):輸入陣列(Input Array)中,大於指定值(Assigned Value)的元素,應該被放到輸出陣列(Output Array)。


 圖二

不過這一次,我們不使用圖一所示的動態成長手法,而是採用預先配置(Preallocating)陣列的策略,如圖三所示:


圖三

區塊1是利用Initialize Array先配置出一個"夠大"的陣列,在這個應用中,輸出陣列(Output Array)最大只可能和輸入陣列(Input Array)相同,因此利用Array Size來將輸出陣列的設定為與輸入陣列相同大小。此處將輸出陣列內的元素值先設為NaN,只是因為NaN不可能出現在輸入陣列中,在開發過程中方便除錯而已,各位讀者可依實際需求設定為任意數值。

區塊2,則是利用Replace Array Subset將元素寫入正確的位置,取代原本的NaN。取代一定是由位置0、位置1、位置2......一路往下依此類推,因此區塊三利用Shift Register與Increment元件做了一個計數器,來記錄當前要覆蓋的位置。最後來到區塊4,當所有元素都寫入都完成後,利用Split 1D Array與計數器把最後覆蓋位置以下的所有元素都砍掉,然後將剩下的部分輸出,即圓滿達成這個應用的需求。

為了證明預先配置真的比較有效率,以下利用 Tick Count(ms) 來進行 Benchmarking,我們分別將圖一的解法 DynamicallyOutputArrayElement.vi 與圖三的解法DynamicallyOutputArrayElement_Preallocate.vi 丟進圖四中 BenchmarkTemplate.vi 內的空白For Loop中,來檢驗它們的執行時間。由於資料量小,單一次的執行時間遠小於 1 ms 無法看出顯著區別,因此以下的 Benchmark 測驗是連續運算十萬次後再取平均。所得到的執行結果分別如圖五與圖六所示。

圖四:待測VI放入空白For Loop中


圖五:動態成長手法,DynamicallyOutputArrayElement.vi


圖六:預先配置陣列,DynamicallyOutputArrayElement_Preallocate.vi

也就是說,以預先配置策略撰寫的VI,平均單次執行時間僅需動態成長手法的八分之一(6415/804 = 7.98)。而請記得,這個差距還只是在輸入矩陣僅有8個元素的場合(見圖二),如果真的操作到大型陣列,兩種策略的差距可能會更顯著!

動態成長手法簡潔易用,但程式執行效率較差;預先配置雖然速度較快,但程式所需步驟較多。一般來說,遇到內含幾百幾千個元素的陣列,動態成長手法已綽綽有餘;碰到上萬個元素,或是對執行時間較為嚴苛的應用時,才會採用預先配置手法。以上是我的一些心得,供各位讀者參考。


程式載點2:DynamicallyOutputArrayElement_Preallocate.vi
程式載點3:BenchmarkTemplate.vi

延伸閱讀
動態地讓Array Size增長 - Part 1
動態地讓Array Size增長 - Part 2

沒有留言:

張貼留言