How Interfaces Work in Go
research!rsc: Go Data Structures: Interfaces https://research.swtch.com/interfaces
How Interfaces Work in Go https://www.tapirgames.com/blog/golang-interface-implementation
This article will introduce the internal implementation of interface types by the official Go compiler.
- garbage collection,
- synchronization,
- address alignment.
The Internal Definition Of Interface Types
type _interface struct {
dynamicTypeInfo *_implementation
dynamicValue unsafe.Pointer // unsafe.Pointer means
// *ArbitraryType in Go.
}
The internal _implementation
type is declared like
type _implementation struct {
itype *_type // the interface type.
dtype *_type // the dynamic type, which must implement itype.
methods []*_func // the methods which are defined on dtype
// and each of them implements a
// corresponding method declared in itype.
}
From the definitions, we know that each interface value contains two pointer fields. The dynamicValue
field stores the dynamic value information, and the dynamicTypeInfo
field stores the implementation information. dynamicTypeInfo.itype
stores the type information of the interface value and dynamicTypeInfo.dtype
stores the type information of the dynamic value.
The dynamicTypeInfo
field of an interface value may be nil
, which means nothing is stored in the interface value. For this case, the dynamicValue
field must be also nil
. We can also say the dynamic value of the interface value is untyped nil
for this case.
For the official Go compiler and runtime, a non-nil dynamicValue
field value may store
- the address of the dynamic value if the dynamic type is not a pointer type, or
- the dynamic value itself if the dynamic type is a pointer type.
Surely, it is not essential to make the exception for pointer dynamic values. This is just a compiler optimization. We can get why it is an optimization in following sections. (BTW, about more current and future optimizations in the official interface implementation, please read this article.)
Other involved internal types are declared like:
type _func struct {
name string
methodSig uint // two methods with the same signature have
// the same signature id. Receiver parameter
// doesn't contribute to this signature.
funcSig uint // receiver parameter accounts to this signature.
// other information ...
}
type _type struct {
name string // type name
id uint32 // each type has unique id
flags uint32 // comparable? isPointer?
size uintptr // value size in bytes
kind uint8 //
methods []*_func // the methods are sorted
// by methodSig and name.
// more information ...
}
const (
// flags
TypeFlag_Comparable = 1 << 0
TypeFlag_IsPointer = 1 << 1
TypeFlag_IsString = 1 << 2
)
func (t *_type) IsComparable() bool {
return t.flags & TypeFlag_Comparable != 0
}
func (t *_type) IsPointer() bool {
return t.flags & TypeFlag_IsPointer != 0
}
func (t *_type) IsString() bool {
return t.flags & TypeFlag_IsString != 0
}
Some fields of above types are not listed, for they are unrelated to this article.
Here is the function to get an _implementation
value from an interface type and a non-interface type:
// global table
var cachedImpls = map[uint64]*_implementation{}
// itype must be an interface type and
// dtype must be a non-interface type.
// Return nil if dtype doesn't implement itype.
// Must not return nil if dtype implements itype.
func getImpl (itype *_type, dtype *_type) *_implementation {
var key = uint64(itype.id) << 32 | uint64(dtype.id)
var impl = cachedImpls[key]
if impl == nil {
// for each (dtype, itype) pair, the implementation
// method table is only calculated most once at
// run time. The calculation result will be cached.
var numMethods = len(itype.methods)
var methods = make([]*_func, numMethods)
// find every implemented methods.
// The methods of itype and dtype are both sorted
// by methodSig and name.
var n = 0
var i = 0
for _, im := range itype.methods {
for i < len(dtype.methods) {
tm := dtype.methods[i]
i++
// Here, for simplicity, assume
// all methods are exported.
if tm.methodSig < im.methodSig {
continue
}
if tm.methodSig > im.methodSig {
// im method is not implemented
return nil
}
if tm.name < im.name {
continue
}
if tm.name > im.name {
// im method is not implemented
return nil
}
methods[n] = tm
n++
break
}
}
// dtype doesn't implement all methods of itype
if n < numMethods {
return nil
}
// dtype implements itype.
// create and cache the implementation.
impl = &_implementation{
dtype: dtype,
itype: itype,
methods: methods,
}
cachedImpls[key] = impl
}
return impl
}
This function will be called in the value conversions explained in following sections.
In any Go program, at run time, all _implementation
values are cached and stored in a global map and all _type
values are stored in a global immutable array.
interface{}
is used popular in Go programming, the official Go compiler uses a different and more efficient underlying definition for the blank interface than other interface types:
// blank interface
struct {
dynamicType *_type // the dynamic type
dynamicValuePtr unsafe.Pointer // points to the dynamic value
}
To make the explainations simpler, following sections will treat blank interface types as general interface types.
Convert Non-Interface Values To Interface Types
// To call this function, compilers must assure
// 1. itype is an interface type.
// 2. dtype is nil or a non-interface type and implements itype.
// p must be nil if dtype is nil.
// p is a pointer stores the address of a value of dtype if dtype
// is not a pointer type. p stores the value of a value of dtype
// if dtype is a pointer type.
func t2i (itype *_type, dtype *_type, p unsafe.Pointer) _interface {
// dtype is nil means the non-interface value is untyped nil
if dtype == nil {
return _interface {
dynamicValue: nil,
dynamicTypeInfo: nil,
}
}
// the pointer dynamic value optimization, no need to
// allocate the extra memory block.
if dtype.IsPointer() {
return _interface {
dynamicValue: p,
dynamicTypeInfo: getImpl(dtype, itype),
}
}
// for non-pointer dynamic value, runtime must
// allocate an extra memory block to store a copy
// of the non-pointer value.
var t = memoryAlloc(dtype)
memoryCopy(t, p, dtype.size)
return _interface {
dynamicValue: t,
dynamicTypeInfo: getImpl(dtype, itype),
}
}
Compilers will insert a call of this function before
- assigning a non-interface value to an interface value, to convert the non-interface value to the type of the interface value.
- comparing a non-interface value with an interface value, to convert the non-interface value to the type of the interface value.
Convert Interface Values To Other Interface Types
// To call this function, compilers must assure
// 1. itype is an interface type.
// 2. the dynamic value of ivalue is untyped nil
// or the dynamic type of ivalue implements ivalue.
// (the method set of the dynamic type of ivalue must
// must be a super set of the method set of itype).
func i2i (itype *_type, ivalue _interface) _interface {
// the dynamic value of ivalue is untyped nil.
if ivalue.dynamicTypeInfo == nil {
return _interface {
dynamicValue: nil,
dynamicTypeInfo: nil,
} // <=> return ivalue
}
// compilers should avoid calling this function
// for this case.
if ivalue.dynamicTypeInfo.itype == itype {
return ivalue // return a copy of ivalue.
}
// Convert the dynamic value of ivalue to itype.
// Here, the returned interface value and ivalue
// will share the same extra memory block which
// stores the dyanmic value if the dynamic value
// is not a pointer.
return _interface {
dynamicValue: ivalue.dynamicValue,
dynamicTypeInfo: getImpl(
ivalue.dynamicTypeInfo.dtype,
itype,
), // here, the getImpl call never return nil.
}
}
Compilers will call this function before
- assigning an interface value to another interface value, to convert the first interface value to the type of the second interface value.
- comparing an interface value with another interface value, to convert the first interface value to the type of the second interface value.
Compilers should translate converting an interface value to its own type as a no-op.
Assign Interface Values
In an interface value assignment, the destination value must be an interface value, and the type of the source value must implement the destination interface type. The source value may be either a non-interface value or an interface value. As above two sections mentioned, compilers will convert the source value to the destination interface type before the assignment. So in the final assignment, the source value and the destination value have the same type, the destination interface type.
For the current official Go compiler/runtime, there are just two copies for the two fields, dynamicValue
and dynamicTypeInfo
, in the final assignment. So if the dynamic value is non-pointer, the underlying dynamic value memory block, which address is stored in the dynamicValue
field, will be shared between the destination and source interface values. However, this should be viewed as an optimization. Other compilers may not adopt this optimization.
Compare Interface Values
- interface value vs. interface value.
- interface value vs. non-interface value.
- interface value vs. untyped
nil
.
A good compiler should treat the three circumstances differently to get better program performance. Here, for simplicity, we assume non-interface and untyped nil
values will be converted to interface{}
type before making the comparisons. So all comparisons involving interface values can be viewed as comparing two interface values.
Here is the internal function to compare two interface values:
func iCompare (ivalue1 _interface, ivalue2 _interface) bool {
// untyped nil is only equal to untyped nil.
if ivalue1.dynamicTypeInfo == nil {
return ivalue2.dynamicTypeInfo == nil
}
if ivalue2.dynamicTypeInfo == nil {
return false
}
// panic on incomparable dynamic values.
if ! ivalue1.dynamicTypeInfo.dtype.IsComparable() {
panic(ivalue1.dynamicTypeInfo.dtype.name +
" is incomparable")
}
if ! ivalue2.dynamicTypeInfo.dtype.IsComparable() {
panic(ivalue2.dynamicTypeInfo.dtype.name +
" is incomparable")
}
// return false if dynamic type is not the same.
if ivalue1.dynamicTypeInfo.dtype !=
ivalue2.dynamicTypeInfo.dtype {
return false
}
// optimization: early return.
if ivalue1.dynamicValue == ivalue2.dynamicValue {
return true
}
// special case: string comparison
if ivalue1.dynamicTypeInfo.dtype.IsString() {
return stringCompare(
*(*string)(ivalue1.dynamicValue),
*(*string)(ivalue2.dynamicValue),
)
}
// general case: compare all bytes in dynamic value
// memory blocks one by one.
return memoryCompare(
ivalue1.dynamicValue,
ivalue2.dynamicValue,
ivalue1.dynamicTypeInfo.dtype.size,
)
}
This article will not explain how two strings are compared.
Type Assert To Non-Interface Types
// To call this function, compilers must assure
// 1. dtype is a non-interface type.
// 2. outP is nil or stores the address of a value of dtype.
// 3. outOk is nil or stores the address of a bool value.
func assertI2T (ivalue _interface, dtype *_type,
outP *unsafe.Pointer, outOk *bool) {
// dynamic value is untype nil.
if ivalue.dynamicTypeInfo == nil {
// if okay is not present, panic.
if outOk == nil {
panic("interface is nil, not " + dtype.name)
}
// return (zero value, false)
*outOk = false
if outP != nil {
if dtype.IsPointer() {
*outP = nil
} else {
memoryReset(*outP, dtype.size)
}
}
return
}
// assersion fails.
if ivalue.dynamicTypeInfo.dtype != dtype {
// if ok is not present, panic.
if outOk == nil {
panic("interface is " +
ivalue.dynamicTypeInfo.dtype.name +
", not " + dtype.name)
}
// return (zero value, false)
*outOk = false
if outP != nil {
if dtype.IsPointer() {
*outP = nil
} else {
memoryReset(*outP, dtype.size)
}
}
return
}
// assersion succeeds.
if outOk != nil {
*outOk = true
}
if outP == nil {
return
}
// copy dynamic value.
if dtype.IsPointer() {
*outP = ivalue.dynamicValue
} else {
memoryCopy(*outP, ivalue.dynamicValue, dtype.size)
}
}
Type Assert To Interface Types
// To call this function, compilers must assure
// 1. itype is an interface type.
// 2. outI is nil or stores the address of a value of itype.
// 3. outOk is nil or stores the address of a bool value.
func assertI2I (ivalue _interface, itype *_type,
outI *_interface, outOk *bool) {
// dynamic value is untype nil.
if ivalue.dynamicTypeInfo == nil {
// if ok is not present, panic.
if outOk == nil {
panic("interface is nil, not " + itype.name)
}
*outOk = false
if outI == nil {
*outI = _interface {
dynamicValue: nil,
dynamicTypeInfo: nil,
}
}
return
}
// check whether or not the dynamic type implements itype
var impl = getImpl(itype, ivalue.dynamicTypeInfo.dtype)
// assersion fails.
if impl == nil {
// if ok is not present, panic.
if outOk == nil {
panic("interface is " +
ivalue.dynamicTypeInfo.dtype.name +
", not " + itype.name)
}
// return (zero value, false)
*outOk = false
if outI != nil {
*outI = _interface {
dynamicValue: nil,
dynamicTypeInfo: nil,
}
}
return
}
// assersion succeeds.
if outI == nil {
*outOk = true
}
if outI != nil {
*outI = _interface {
dynamicValue: ivalue.dynamicValue,
dynamicTypeInfo: impl,
}
}
}
If the type of the interface value is the asserted interface type, compilers should simply return the interface value.
Call Interface Methods
i
, a call of its nth method (by the order after sorted)
... = i.Method_n(...)
will be translated to
if i.dynamicTypeInfo == nil {
panic("runtime error: nil pointer dereference")
}
if i.dynamicTypeInfo.dtype.IsPointer() {
... = _call(i.dynamicTypeInfo.methods[n], i.dynamicValue, ...)
} else {
... = _call(i.dynamicTypeInfo.methods[n],
*(*unsafe.Pointer)(i.dynamicValue), ...)
}
Golang Interfaces Explained
Logging, interfaces, and allocation
FEBRUARY 6, 2017
This post is about some new compiler optimizations scheduled for Go 1.9, but I want to start with logging.
A couple of weeks ago, Peter Bourgon started a thread on golang-dev about standardizing logging. Logging is pervasive, so performance came up quickly. The go-kit log package uses structured logging, centered on this interface:
type Logger interface {
Log(keyvals ...interface{}) error
}
Sample call:
logger.Log("transport", "HTTP", "addr", addr, "msg", "listening")
Note that everything that goes into a logging call gets converted into an interface. This means that it allocates a lot.
Compare with another structured logger, zap. Zap has uglier call sites, specifically to avoid using interfaces, in order to be zero-allocation:
logger.Info("Failed to fetch URL.",
zap.String("url", url),
zap.Int("attempt", tryNum),
zap.Duration("backoff", sleepFor),
)
The arguments to logger.Info
have type logger.Field
. logger.Field
is a kind of union-ish struct that includes a type and a field each for a string
, an int
, and an interface{}
. Thus interfaces are not necessary to pass the most common kinds of values.
Enough about logging. Why does converting a concrete value to an interface sometime allocate?
Interfaces are represented as two words, a type pointer and a value pointer. Russ Cox wrote a lovely explanation of this, which I will not attempt to repeat. Just go read it.
His post is slightly out of date, however. He points out an obvious optimization: When the value is pointer-sized or smaller, we can just put the value directly into the second interface word. However, with the advent of concurrent garbage collection, that optimization got eliminated. Now the second word in the interface is always a pointer.
Consider:
fmt.Println(1)
Before Go 1.4, this code did not allocate, because the value 1
could be put directly into the second interface word.
That is, the compiler treated it something like this:
fmt.Println({int, 1})
where {typ, val}
represents the two words in an interface.
As of Go 1.4, this code started allocating, because 1
is not a pointer, and the second word must contain a pointer. So instead the compiler+runtime conspired to turn it into something roughly like:
i := new(int) // allocates!
*i = 1
fmt.Println({int, i})
This was painful, and there was much wringing of hands and gnashing of teeth.
The first significant optimization to remove allocations was added a bit later. It kicked in when the resulting interface did not escape. In that case, the temporary value could be put on the stack instead of the heap. Using our example code above:
i := new(int) // now doesn't allocate, as long as e doesn't escape
*i = 1
var e interface{} = {int, i}
// do things with e that don't make it escape
Unfortunately, many interfaces do escape, including those used in calls to fmt.Println
and in our logging examples above.
Happily, Go 1.9 will bring a few more optimizations, in part inspired by the logging conversation. (Unless those optimizations get reverted in the next six months, which is always a possibility.)
The first optimization is to not allocate to convert a constant to an interface. So fmt.Println(1)
will no longer allocate. The compiler puts the value 1
in a readonly global, roughly like this:
var i int = 1 // at the top level, marked as readonly
fmt.Println({int, &i})
This is possible because constants are immutable, and will thus be the same every time the interface conversion is reached, including recursively and concurrently.
This was inspired directly by the logging discussion. In structured logging, many of the arguments are constants– almost certainly all the keys, and probably a few of the values. Recall the go-kit example:
logger.Log("transport", "HTTP", "addr", addr, "msg", "listening")
This code drops from 6 allocations to 1, because five of the arguments are constant strings.
The second new optimization is to not allocate to convert bools and bytes to interfaces. This optimization works by adding a global [256]byte
array called staticbytes
to every binary, where staticbytes[b] = b
for all b. When the compiler wants to put a bool or uint8 or other single-byte value into an interface, instead of allocating, it calculates a pointer into this array. That is:
var staticbytes [256]byte = {0, 1, 2, 3, 4, 5, ...}
i := uint8(1)
fmt.Println({uint8, &staticbytes[i]})
There is a third new optimization proposed that is still under review, which is to not allocate to convert common zero values in an interface. It applies to integers, floats, strings, and slices. This optimization works by checking at runtime whether the value is 0
(or ""
or nil
). If so, it uses a pointer to an existing large chunk of zeroed memory rather than allocating some memory and zeroing it.
If all goes well, Go 1.9 should eliminate a fair number of allocations during interface conversions. But it won't eliminate all of them, which leaves performance still on the table as the logging discussion continues.
The interplay between implementation decisions and APIs is interesting.
Picking an API requires thinking about the performance consequences. It is not an accident that io.Reader
requires/allows callers to bring their own buffer.
Performance is in no small part a consequence of the implementation decisions. We have seen in this post that the implementation details of interfaces can substantially alter what code allocates.
And yet those very implementation decisions depend on what kind of code people write. The compiler and runtime authors want to optimize real, common code. For example, the decision to in Go 1.4 to keep interface values at two words instead of changing them to three, which made fmt.Println(1)
allocate, was based on looking at the kind of code people wrote.
Since the kind of code people write is often shaped heavily by the APIs they use, we have the kind of organic feedback loop that is fascinating and sometimes challenging to manage.
Not a terribly deep observation, perhaps, but there is one takeaway: If you're designing an API and worrying about performance, keep in mind not just what the existing compiler and runtime actually do, but what they could do. Write code for the present, but design APIs for the future.
And if you're not sure, ask. It worked (a bit) for logging.
How Interfaces Work in Go的更多相关文章
- 代码的坏味道(9)——异曲同工的类(Alternative Classes with Different Interfaces)
坏味道--异曲同工的类(Alternative Classes with Different Interfaces) 特征 两个类中有着不同的函数,却在做着同一件事. 问题原因 这种情况往往是因为:创 ...
- 【转】[fix] Wireshark error: There are no interfaces on which a capture can be done. on Mac OS X
I got the following error message when trying to open a network interface for capture using Wireshar ...
- CentOS / Redhat : Configure CentOS as a Software Router with two interfaces
CentOS / Redhat : Configure CentOS as a Software Router with two interfaces Linux can be easily co ...
- 错误:java.util.Map is an interface, and JAXB can't handle interfaces.
问题: 在整合spring+cxf时报错java.util.Map is an interface, and JAXB can't handle interfaces. 解决方法: 将服务端的serv ...
- Wireshark设置interface 时提示“There are no interfaces on which a capture can be done ”
Wireshark设置interface 时提示“There are no interfaces on which a capture can be done ” 解决方法: 今天在电脑上安装了WIR ...
- iis 使用 LocalDB 报错:provider: SQL Network Interfaces, error: 50
在使用asp.net core读取localdb数据库时,报以下错误: 在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误.未找到或无法访问服务器.请验证实例名称是否正确并且 S ...
- System.Data.SqlClient.SqlException: 在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误。未找到或无法访问服务器。请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接。 (provider: SQL Network Interfaces, error: 26 - 定位指定的服务器/实例时出错)
A network-related or instance-specific error occurred while establishing a connection to SQL Server. ...
- Go Data Structures: Interfaces
refer:http://research.swtch.com/interfaces Go Data Structures: Interfaces Posted on Tuesday, Decembe ...
- TypeScript - Interfaces
简介 关注于数据值的 ‘shape’的类型检查是TypeScript核心设计原则.这种模式有时被称为‘鸭子类型’或者‘结构子类型化’. . 在TypeScript中接口interfaces的责任就是命 ...
- ubuntu /etc/network/interfaces 中配置虚拟链路
ubuntu /etc/network/interfaces 中配置虚拟链路 平常做一些关于网络的测试时,像一些需要在二层上运行的功能,一个网卡不足够的情况下,可使用 ip link 工具加一些虚拟的 ...
随机推荐
- 快速沃尔什变换 (FWT)学习笔记
证明均来自xht37 的洛谷博客 作用 在 \(OI\) 中,\(FWT\) 是用于解决对下标进行位运算卷积问题的方法. \(c_{i}=\sum_{i=j \oplus k} a_{j} b_{k} ...
- 实现连续登录X天送红包这个连续登录X天算法
实现用户只允许登录系统1次(1天无论登录N次算一次) //timeStamp%864000计算结果为当前时间在一天当中过了多少秒 //当天0点时间戳 long time=timeStamp-timeS ...
- 基于websocket的netty demo
前面2文 基于http的netty demo 基于socket的netty demo 讲了netty在http和socket的使用,下面讲讲netty如何使用websocket websocket是h ...
- 整数划分(硬币问题)(dp)
题目描述 考试时思路 本菜狗考试的时候,第一扁打了纯dfs,15分拿了9分 后面看了时限400ms,多组数据,以为会卡常数,然后就想着先dp打表然后再直接O(1)查询 后面发现自己想多了,数据有点水- ...
- Python学习-小黑屋游戏
给大家分享一下有趣的游戏,在大一上学期学习的内容里,小黑屋比较好玩. 1.导入函数库 先导入random.time两个函数库的使用来达到随机生成人物.生成人物加载时间的目的. import rando ...
- 在.NET Core中使用Channel(二)
在我们之前的文章中,看了一些非常简单的例子来说明Channel是如何工作的,我们看到了一些非常漂亮的特性,但大多数情况下它与其他某某Queue实现非常相似.让我们进入一些更高级的话题.我说的是高级,但 ...
- 风炫安全WEB安全学习第二十五节课 利用XSS键盘记录
风炫安全WEB安全学习第二十五节课 利用XSS键盘记录 XSS键盘记录 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源.所以xyz.com下的js脚本采用a ...
- 利用PHP递归 获取所有的上级栏目
/** * 获取所有的上级栏目 * @param $category_id * @param array $array * @return array * @author 宁佳兵 <meilij ...
- TCP连接的建立与释放(超详细)
前言:在计算机网络协议中,TCP只是其中一个,然而在网络使用中,TCP也是最离不开的协议之一,它的重要性毋庸置疑,最最重要的是,面试的重点就是它啊,呜呜~~,今天我们一起来看下TCP的连接建立与释放, ...
- 【Java基础】常用类
常用类 字符串相关的类 String类:代表字符串,使用一对 "" 引起来表示. public final class String implements java.io.Seri ...