每当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++那样以通用的宏就可以实现。

使您生还好之方案,欢迎留言告知自己!