lru算法C语言结构体指针,在Golang里如何实现结构体成员指针到结构体自身指针的转换...

本文探讨了在Go语言中如何实现类似C语言中结构体指针转换的功能,并通过示例代码展示了不同实现方式的性能差异。通过创建无用对象、利用已有指针等方式优化了转换函数,最终实现与原始C语言宏定义类似的功能,同时关注了性能开销。通过全局变量缓存偏移量进一步减少了计算成本,达到最佳性能。
摘要由CSDN通过智能技术生成

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

如果你有更好的方案,欢迎留言告诉我!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值