2016-09-14 35 views
3

當定義與interface{}類型(例如Printf)的可變參數的函數,參數顯然隱式轉換的接口的實例。去隱式轉換到接口做內存分配?

這是否意味着轉換內存分配?此轉換速度快嗎?當關注代碼效率時,我應該避免使用可變參數函數嗎?

回答

2

我發現關於圍棋的接口內存分配最好的解釋還是本文從羅斯考克斯,核心圍棋程序員之一。閱讀它是非常值得的。

http://research.swtch.com/interfaces

我拿起一些最有趣的部分:

存儲在接口

值可能是任意大,但只有一個 字是專門爲在接口持有價值結構,所以 該分配在堆上分配了一塊內存,並在單字槽中記錄了指針 。

...

調用fmt.Printf(),轉到編譯器產生從itable調用 適當函數指針,經過該接口 值的數據字作爲碼函數的第一個(在本例中,只有) 的說法。

Go的動態類型轉換意味着 編譯器或鏈接器預先計算所有可能的行爲是不合理的:太多的(接口類型,具體類型)對,並且大多數將不需要。 相反,編譯器爲每個 具體類型(如Binary或int或func(map [int] string))生成類型描述結構。在其他 元數據中,類型描述結構包含由該類型實現的 方法的列表。

...

接口運行時間通過查找具體類型的 方法表中的接口類型的方法表中列出的每個方法 來計算可行。運行時在生成它之後緩存它可用,所以 這個對應只需要計算一次。

...

如果涉及的接口類型是空的,它沒有任何方法,那麼 itable無濟於事除了指針保持原來的 類型。在這種情況下,可以刪除該值,並且該值可以直接指向 。

因爲Go有動態方法查找的靜態類型提示,它可以將查找從呼叫站點移回到值存儲在界面中的位置。

+1

我給你的答案,因爲引用的文字解釋了爲什麼將值轉換爲接口可能是「昂貴」,應該避免。此轉換不是免費的。 – chmike

0

去傳遞參數copy_by_value,所以無論如何它的內存分配。如果可能,你總是應該儘量避免使用interface {}。在描述的情況下,你的函數將需要反映參數來使用它們。反射是相當昂貴的操作,這就是爲什麼fmt.Printf()是如此之慢。

+0

我很確定Printf不使用反射,而是使用類型斷言來代替[https://golang.org/ref/spec#Type_assertions],這在使用接口時會很有效。我目前的假設是,關鍵點是將非接口值轉換爲接口值。 @Simo Endre的答案解釋了爲什麼它很慢。這不僅是由於可能需要的堆分配。這是因爲查找方法。 – chmike

1

轉換爲一個interface{}是從包含在一個切片,並且可以是任何類型的可變參數的單獨的概念。然而,只要不逃到堆中(在GC工具鏈中),這些都可能是分配意義上的自由。

fmt函數Printf中看到的多餘分配將從反思而不是使用interface{}或可變參數。

如果你使用效率而言,雖然,避免間接總是比沒有更有效,所以使用正確的值類型會產生更高效的代碼。儘管如此,差異可能很小,因此先對代碼進行基準測試,然後再進行較小的優化。

+0

你的答案不夠清楚。如果我將一個接口作爲參數傳遞給一個可變參數函數,那麼將執行一個簡單的參考拷貝。這很便宜。但是,如果我傳遞另一個值,如整數或切片,則必須轉換爲接口數據結構。這個接口數據結構是分配在堆還是堆棧上?堆分配通常會造成顯着的性能損失。然而,我在最後一段中遵循了您的建議,並擺脫了可變API。 – chmike

+0

@chmike:在Go中沒有這種東西作爲「參考副本」。 Go中的所有東西都是價值,所有東西都是按價值傳遞的。可變參數對接口沒有幫助,它只是簡單的語法糖,將它添加到這個對話中只會混淆問題。如果將接口傳遞給接口參數,則接口值將被複製爲2個字。如果可能的話,這些都是堆棧分配的(如果它們不能逃脫的話)。 – JimB

+0

有一個誤解。我將接口值中的兩個指針稱爲引用。這兩個指針確實被複制到堆棧上。如果參數不是接口,則必須將其轉換爲接口。我的問題當然是考慮到'[] interface {}'類型的可變參數的參數不是接口並且需要轉換爲接口的情況。我主要關心的是像int或string這樣的基類型。 – chmike