每当Golang里怎么实现结构体成员指针到结构体自身指针的变换

原文地址:http://goworldgs.com/?p=37

以C语言中产生一个经典的宏定义,可以用组织体struct内部的某个成员的指针转化为结构体自身之指针。下面是一个事例,通过FIELD_OFFSET宏计算结构体内一个字段的摇,函数getT可以于一个F*的指针获得相应的T*对象。

struct F {
    int c;
    int d;
}

struct T{
    int a;
    int b;
    struct F f;
}

#define FIELD_OFFSET(type, field) ((int)(unsigned char *)(((struct type *)0)->field))

struct T* getT(struct F* f) {
    return (T*)((unsigned char *)f - FIELD_OFFSET(T, F))
}

当Golang中能否实现均等的成效?尝试写如下的代码:

type T struct {
    a int
    b int
    f F
}

type F struct {
    c int
    d int
}

func (m *F) T1() *T {
    var dummy *T
    fieldOffset := uintptr(unsafe.Pointer(&dummy.f)) - uintptr(unsafe.Pointer(dummy))
    return (*T)(unsafe.Pointer((uintptr)(unsafe.Pointer(m)) - fieldOffset))
}

编译通过,运行!panic: runtime error: invalid memory address or nil
pointer dereference。这里dummy
*T是nil,虽然代码并无看dummy所指向的内容,但是Golang依然未容许这样用此指针。

既然如此Golang不允许行使nil指针,那么我们可由此创办一个不行的T对象来绕开是题材,代码如下:

func (m *F) T2() *T {
    var dummy T
    fieldOffset := uintptr(unsafe.Pointer(&dummy.f)) - uintptr(unsafe.Pointer(&dummy))
    return (*T)(unsafe.Pointer((uintptr)(unsafe.Pointer(m)) - fieldOffset))
}

测试证明这代码可以健康干活,并且我们可动用另外一个函数TBad来进行性比:

func (m *F) TBad() *T {
    return (*T)(unsafe.Pointer(uintptr(unsafe.Pointer(m)) - 16))
}

func BenchmarkGetPtrByMemberPtr_T2(b *testing.B) {
    var t T
    for i := 0; i < b.N; i++ {
        if &t != t.f.T2() {
            b.Fatal("wrong")
        }
    }
}

func BenchmarkGetPtrByMemberPtr_TBad(b *testing.B) {
    var t T
    for i := 0; i < b.N; i++ {
        if &t != t.f.TBad() {
            b.Fatal("wrong")
        }
    }
}

测试结果:T2和TBad的运行开销分别吗:1.44 ns/op和0.85 ns/op。

考虑到T2为什么会比TBad有双重要命的付出,我们怀疑T2里老是都需要以heap上缔造一个T对象。如果T对象的大小很要命之时光,创建T对象的出啊会见附加,我们得经增大结构体T的分寸来展开验证。我们以T结构体的概念修改也:

type T struct {
    a int
    b int
    f F
    e [1024]byte
}

再也运行发现T2的开支增大到37.8
ns/op。那么如何才会排T结构体大小对之函数的震慑?Golang不允我们采用nil指针,是匪是咱特需要伪造一个*T的非nil指针即可?尝试写如下代码并展开测试:

func (m *F) T3() *T {
    var x struct{}
    dummy := (*T)(unsafe.Pointer(&x))
    fieldOffset := uintptr(unsafe.Pointer(&dummy.f)) - uintptr(unsafe.Pointer(dummy))
    return (*T)(unsafe.Pointer((uintptr)(unsafe.Pointer(m)) - fieldOffset))
}

T3的开支降低到1.14 ns/op,接近最抢之TBad的0.85
ns/op。更进一步的,我们得直接利用*F指针作为dummy,代码如下:

func (m *F) T4() *T {
    dummy := (*T)(unsafe.Pointer(m))
    fieldOffset := uintptr(unsafe.Pointer(&dummy.f)) - uintptr(unsafe.Pointer(dummy))
    return (*T)(unsafe.Pointer((uintptr)(unsafe.Pointer(m)) - fieldOffset))
}

只是测试表明T4和T3的支出了同,都是1.14 ns/op。

自从目前为止,T3和T4的实现性能非常好,只于TBad里高一点点。推测原因是TBad不欲算F类型field的皇,在C语言里FIELD_OFFSET宏也是于编译时开展计算,但是当T3和T4中需要算同一糟f
*F字段在T结构体中之摇。我们好利用一个全局变量来保存字段的摆,这样虽未待每次都进展计算,代码如下:

var fieldOffset uintptr

func init() {
    dummy := (*T)(unsafe.Pointer(&fieldOffset))
    fieldOffset = uintptr(unsafe.Pointer(&dummy.f)) - uintptr(unsafe.Pointer(dummy))
}
func (m *F) T5() *T {
    return (*T)(unsafe.Pointer((uintptr)(unsafe.Pointer(m)) - fieldOffset))
}

测试表明T5的付出和TBad一样,都是0.85 ns/op,这个相应既是极端了。

鉴于Go语言没有提供泛型机制,所以每个需要为此到者意义的切近都需要定义自己之更换函数,而休克如C/C++那样使通用的宏就可以兑现。

使你闹再度好的方案,欢迎留言告知我!