程式效能優化-區域性性原理

woai3c發表於2019-01-17

更多文章

概念

一個編寫良好的計算機程式常常具有良好的區域性性,它們傾向於引用鄰近於其他最近引用過的資料項的資料項,或者最近引用過的資料項本身,這種傾向性,被稱為區域性性原理。有良好區域性性的程式比區域性性差的程式執行得更快。

區域性性通常有兩種不同的形式:

  • 時間區域性性

在一個具有良好時間區域性性的程式中,被引用過一次的記憶體位置很可能在不遠的將來被多次引用。

  • 空間區域性性

在一個具有良好空間區域性性的程式中,如果一個記憶體位置被引用了一次,那麼程式很可能在不遠的將來引用附近的一個記憶體位置。

時間區域性性示例

function sum(arry) {
    let i, sum = 0
    let len = arry.length

    for (i = 0; i < len; i++) {
        sum += arry[i]
    }

    return sum
}

在這個例子中,變數sum在每次迴圈迭代中被引用一次,因此,對於sum來說,具有良好的時間區域性性

空間區域性性示例

具有良好空間區域性性的程式

// 二維陣列 
function sum1(arry, rows, cols) {
    let i, j, sum = 0

    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            sum += arry[i][j]
        }
    }
    return sum
}

空間區域性性差的程式

// 二維陣列 
function sum2(arry, rows, cols) {
    let i, j, sum = 0

    for (j = 0; j < cols; j++) {
        for (i = 0; i < rows; i++) {
            sum += arry[i][j]
        }
    }
    return sum
}

再回頭看一下時間區域性性的示例,像示例中按順序訪問一個陣列每個元素的函式,具有步長為1的引用模式。

如果在陣列中,每隔k個元素進行訪問,就稱為步長為k的引用模式。

一般而言,隨著步長的增加,空間區域性性下降。

這兩個例子有什麼區別?區別在於第一個示例是按照列順序來掃描陣列,第二個示例是按照行順序來掃描陣列。

陣列在記憶體中是按照行順序來存放的,結果就是按行順序來掃描陣列的示例得到了步長為rows的引用模式;
而對於按列順序來掃描陣列的示例來說,其結果是得到一個很好的步長為1的引用模式,具有良好的空間區域性性。

效能測試

執行環境

  • cpu: i5-7400
  • 瀏覽器: chrome 70.0.3538.110

對一個長度為9000的二維陣列(子陣列長度也為9000)進行10次空間區域性性測試,時間(毫秒)取平均值,結果如下:

所用示例為上述兩個空間區域性性示例

按列排序 按行排序
124 2316

從以上測試結果來看,二維陣列按列順序訪問比按行順序訪問快了1個數量級的速度。

總結

  • 重複引用相同變數的程式具有良好的時間區域性性
  • 對於具有步長為k的引用模式的程式,步長越小,在記憶體中以大步長跳來跳去的程式空間區域性性會很差

測試程式碼

const arry = []
let [num, n, cols, rows] = [9000, 9000, 9000, 9000]
let temp = []

while (num) {
    while (n) {
        temp.push(n)
        n--
    }
    arry.push(temp)
    n = 9000
    temp = []
    num--
}

let last, now, val

last = new Date()
val = sum1(arry, rows, cols)
now = new Date()
console.log(now - last)
console.log(val)

last = new Date()
val = sum2(arry, rows, cols)
now = new Date()
console.log(now - last)
console.log(val)

function sum1(arry, rows, cols) {
    let i, j, sum = 0

    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            sum += arry[i][j]
        }
    }
    return sum
}

function sum2(arry, rows, cols) {
    let i, j, sum = 0

    for (j = 0; j < cols; j++) {
        for (i = 0; i < rows; i++) {
            sum += arry[i][j]
        }
    }
    return sum
}

參考資料

深入理解計算機系統

相關文章