2017-02-23 23 views
2

我喜歡swift的值語義,但我擔心變異函數的性能。假設我們有以下structswift中的變異結構函數是否創建自我的新副本?

struct Point { 
    var x = 0.0 
    mutating func add(_ t:Double){ 
     x += t 
    } 
} 

現在假設我們創建了一個Point和變異它像這樣:

var p = Point() 
p.add(1) 

現在確實在內存中的現有結構得到突變,或者是self與替換爲新實例在

self = Point(x:self.x+1) 
+0

對於值類型,「內存中的現有結構發生變異」和「自身被新實例替換」意味着同樣的事情。該結構存在於堆棧中的一些字節數。無論你改變結構的單個字段,還是分配一個新的結構,結構仍然存在於這些相同的字節中。 – Alexander

回答

4

現在確實在內存中的現有結構得到突變或自我替換爲新實例

從概念上講,這兩個選項完全相同。我將使用這個示例結構,它使用UInt8而不是Double(因爲它的位更易於可視化)。

struct Point { 
    var x: UInt8 
    var y: UInt8 

    mutating func add(x: UInt8){ 
     self.x += x 
    } 
} 

,並假設我創建這個結構的新實例:

var p = Point(x: 1, y: 2) 

此靜態分配堆棧上的一些記憶。它會是這個樣子:

00000000 00000001 00000010 00000000 
<------^ ^------^ ^------^ ^-----> 
other | self.x | self.y | other memory 
      ^----------------^ 
      the p struct 

讓我們來看看在這兩種情況下會發生什麼,當我們要求p.add(x: 3)

我們的結構:

  1. 現有結構是就地突變在內存中將如下所示:

    00000000 00000100 00000010 00000000 
    <------^ ^------^ ^------^ ^-----> 
    other | self.x | self.y | other memory 
         ^----------------^ 
         the p struct 
    
  2. Self is rep鑲有一個新的實例:

    我們在內存結構看起來就像這樣:

    00000000 00000100 00000010 00000000 
    <------^ ^------^ ^------^ ^-----> 
    other | self.x | self.y | other memory 
         ^----------------^ 
         the p struct 
    

注意,有兩種情況之間沒有區別。這是因爲給自我賦予新的價值導致就地變異。 p始終是堆棧上相同的兩個字節的內存。爲p指定一個新值只會替換這兩個字節的內容,但它仍然是相同的兩個字節。

現在有可以是這兩種情況之間的一個區別,它處理初始值設定項的任何可能的副作用。假設這是我們的結構,而不是:

struct Point { 
    var x: UInt8 
    var y: UInt8 

    init(x: UInt8, y: UInt8) { 
     self.x = x 
     self.y = y 
     print("Init was run!") 
    } 

    mutating func add(x: UInt8){ 
     self.x += x 
    } 
} 

當您運行var p = Point(x: 1, y: 2),你會看到Init was run!打印(如預期)。但是,當您運行p.add(x: 3)時,您會看到沒有進一步的打印。這告訴我們初始化器不是新的。

+0

好吧,我明白無論如何,結果狀態將是相同的,但是如果'self'被替換不是自我。 y也被重新初始化爲相同的值,這意味着浪費時間,或者編譯器足夠聰明,足以讓self.y單獨離開,只更新self.x? – gloo

+0

它只會改變x – Alexander

0

我這樣做:

import Foundation 

struct Point { 
    var x = 0.0 
    mutating func add(_ t:Double){ 
    x += t 
    } 
} 

var p = Point() 

withUnsafePointer(to: &p) { 
    print("\(p) has address: \($0)") 
} 

p.add(1) 

withUnsafePointer(to: &p) { 
    print("\(p) has address: \($0)") 
} 

和在輸出端獲得:

點(x:0.0)具有地址:0x000000010fc2fb80

點(x:1.0)具有地址:0x000000010fc2fb80

考慮到存儲器地址沒有改變,I b等結構發生了變化,而不是被取代。

要完全替換某些東西,必須使用另一個內存地址,因此將原始內存地址中的對象複製回來毫無意義。

+1

結構不是堆中的對象:)你可以重新指定它們的值,但它們總是與它們開始的靜態分配的內存部分相同 – Alexander

2

我覺得值得一看(從合理的高水平)編譯器在這裏做什麼。如果我們看一看規範SIL用於發射:

struct Point { 
    var x = 0.0 
    mutating func add(_ t: Double){ 
     x += t 
    } 
} 

var p = Point() 
p.add(1) 

我們可以看到,add(_:)方法時發出爲:

// Point.add(Double) ->() 
sil hidden @main.Point.add (Swift.Double) ->() : 
      [email protected](method) (Double, @inout Point) ->() { 
// %0            // users: %7, %2 
// %1            // users: %4, %3 
bb0(%0 : $Double, %1 : $*Point): 

    // get address of the property 'x' within the point instance. 
    %4 = struct_element_addr %1 : $*Point, #Point.x, loc "main.swift":14:9, scope 5 // user: %5 

    // get address of the internal property '_value' within the Double instance. 
    %5 = struct_element_addr %4 : $*Double, #Double._value, loc "main.swift":14:11, scope 5 // users: %9, %6 

    // load the _value from the property address. 
    %6 = load %5 : $*Builtin.FPIEEE64, loc "main.swift":14:11, scope 5 // user: %8 

    // get the _value from the double passed into the method. 
    %7 = struct_extract %0 : $Double, #Double._value, loc "main.swift":14:11, scope 5 // user: %8 

    // apply a builtin floating point addition operation (this will be replaced by an 'fadd' instruction in IR gen). 
    %8 = builtin "fadd_FPIEEE64"(%6 : $Builtin.FPIEEE64, %7 : $Builtin.FPIEEE64) : $Builtin.FPIEEE64, loc "main.swift":14:11, scope 5 // user: %9 

    // store the result to the address of the _value property of 'x'. 
    store %8 to %5 : $*Builtin.FPIEEE64, loc "main.swift":14:11, scope 5 // id: %9 

    %10 = tuple(), loc "main.swift":14:11, scope 5 
    %11 = tuple(), loc "main.swift":15:5, scope 5 // user: %12 
    return %11 : $(), loc "main.swift":15:5, scope 5 // id: %12 
} // end sil function 'main.Point.add (Swift.Double) ->()'

(通過運行xcrun swiftc -emit-sil main.swift | xcrun swift-demangle > main.silgen

重要這裏的事情是Swift如何處理隱含的self參數。您可以看到它已經作爲@inout參數發出,這意味着它將通過參考傳遞到函數中。

爲了執行x屬性的突變,所述struct_element_addr SIL指令,以便用於查找它的地址,然後將Double的底層_value屬性。然後通過store指令簡單地將結果的雙精度值存儲回該地址。

這意味着,該方法add(_:)能夠直接改變存儲器px屬性的值,而無需創建的Point任何中間實例。

相關問題