能在編碼時作的事,就不要推遲到運行時

從 SF 慢慢把文章搬過來。。。html

segmentfault.com/a/119000001…git

TL.DR

軟件是一個巨大的有限狀態機。github

工程師平常作的 bug 修復、性能調優,本質上就是儘量保證代碼處於有序狀態下。儘量多地將狀態固定在編碼時,就就減小了運行期的狀態,使得軟件的狀態總數減小了。編程

狀態總數少了,錯誤就少了,性能也提高了。swift

減小錯誤

以矩陣相乘爲例。segmentfault

矩陣相乘

如圖所示,要保證第一個矩陣中的列數必須與第二個矩陣中的行數相同。簡單的作法是作運行時檢查佈局

struct Matrix {
  let rows: Int
  let columns: Int
}

func multiply(m1: Matrix, _ m2: Matrix) -> Matrix? {
  // do the matrices have the correct sizes?
  precondition(m1.columns == m2.rows)
  
  // bunch of math...
}
複製代碼

更好的作法是在編碼時,就不容許出現行列不等的狀況性能

protocol Dimension {
  static var size: Int { get set }
}

func multiply<U: Dimension, V: Dimension, W: Dimension> (m1: Matrix<U,V>, _ m2: Matrix<V,W>) -> Matrix<U,W> {
  // bunch of math...
  return Matrix<U,W>()
}
複製代碼

運行結果優化

struct NumExamples: Dimension { static var size = 20 }
struct NumFeatures: Dimension { static var size = 10 }
struct OneDimensional: Dimension { static var size = 1 }

let A = Matrix<NumExamples, NumFeatures>()
let B = Matrix<NumFeatures, OneDimensional>()

let C = multiply(A, B)   // yay!

let D = multiply(B, A)   // compiler error
複製代碼

完整的優化過程不在本文的討論範圍內,感興趣的能夠看這裏ui

提高性能

以 OC Runtime 的 Non fragile ivars 爲例。 與 C、C++以來對象的內存結構不一樣,爲了實現 ABI 兼容,OC 的對象並不直接存儲成員變量的指針,每一個成員變量都須要兩次尋址才能訪問到。好比要獲取 obj 的第 n 個變量的偏移地址,就要

*((&obj->isa.cls->data()->ro->ivars->first)[N]->offset);
複製代碼

看起來慢爆了。

在實現上,LLVM 爲每一個類的每一個成員變量都分配了一個全局變量,用於存儲該成員變量的偏移值。這樣,訪問每一個變量須要兩次尋址,先獲取全局變量,再取全局變量的值做爲地址找到真正的變量。

編譯後的

obj->myInt = 42;
複製代碼

對應於以下 C 代碼

int32_t g_ivar_MyClass_myInt = 40;  // 全局變量
*(int32_t *)((uint8_t *)obj + g_ivar_MyClass_myInt) = 42;
複製代碼

這就是爲何 ivar_t.offset 用 int 指針來存儲偏移值,而不是直接放一個 int 的緣由。

struct ivar_t {
    int32_t *offset;  // 注意,這裏是指針
    const char *name;
    const char *type;
    
    //...
}
複製代碼

真正存放偏移值的地址是固定不變的,在編譯時就肯定了下來。所以才能用區區 2 條指令搞定動態佈局的成員變量。

參考連接

objc explain: Non-fragile ivars Dynamic ivars: solving a fragile base class problem 編程世界的熵增原理

相關文章
相關標籤/搜索