众所周知,go没有联合类型,只能通过接口模拟。
我尝试了两种方法来模拟联合,但结果远不如C。
package main
import (
"fmt"
"time"
)
type U interface {
i32() int32
i16() int16
}
type i32 int32
func (u i32) i32() int32 {
return int32(u)
}
func (u i32) i16() int16 {
return int16(u)
}
type i16 int16
func (u i16) i32() int32 {
return int32(u)
}
func (u i16) i16() int16 {
return int16(u)
}
func test() (total int64) {
type A struct {
t int32
u interface{}
}
a := [...]A{{1, int32(100)}, {2, int16(3)}}
for i := 0; i < 5000000000; i++ {
p := &a[i%2]
switch p.t {
case 1:
total += int64(p.u.(int32))
case 2:
total += int64(p.u.(int16))
}
}
return
}
func test2() (total int64) {
type A struct {
t int32
u U
}
a := [...]A{{1, i32(100)}, {2, i16(3)}}
for i := 0; i < 5000000000; i++ {
p := &a[i%2]
switch p.t {
case 1:
total += int64(p.u.i32())
case 2:
total += int64(p.u.i16())
}
}
return
}
type testfn func() int64
func run(f testfn) {
ts := time.Now()
total := f()
te := time.Now()
fmt.Println(total)
fmt.Println(te.Sub(ts))
}
func main() {
run(test)
run(test2)
}
结果:
257500000000
1m23.508223094s
257500000000
34.95081661s
方法方式更好,而且类型转换方式会花费更多的CPU时间。
C版:
#include <stdio.h>
struct A {
int t;
union {
int i;
short v;
} u;
};
long test()
{
struct A a[2];
a[0].t = 1;
a[0].u.i = 100;
a[1].t = 2;
a[1].u.v = 3;
long total = 0;
long i;
for (i = 0; i < 5000000000; i++) {
struct A* p = &a[i % 2];
switch(p->t) {
case 1:
total += p->u.i;
break;
case 2:
total += p->u.v;
break;
}
}
return total;
}
int main()
{
long total = test();
printf("%ld\n", total);
}
结果:
257500000000
real 0m5.620s
user 0m5.620s
sys 0m0.000s
联合类型对许多应用程序很有用,例如:网络协议可能包含变体具体类型。 因此,联合数据的访问可能会成为应用程序的瓶颈。
有人可以帮忙吗?感谢。
答案 0 :(得分:8)
您可以使用数组将单个int32
表示为两个int16
,然后使用班次as Rob Pike recommends汇总它们:
func test3() (total int64) {
type A struct {
t int32
u [2]int16
}
a := [...]A{
{1, [2]int16{100, 0}},
{2, [2]int16{3, 0}},
}
for i := 0; i < N; i++ {
p := &a[i%2]
switch p.t {
case 1:
total += int64(p.u[0]<<0 | p.u[1]<<8)
case 2:
total += int64(p.u[0])
}
}
return
}
使用原始Go编译器,它运行速度比C版慢2倍,而使用gccgo(-O3)运行速度与C版一样快。
请注意,这种方法假定为小端整数。您需要切换big-endian架构的班次顺序。
此外,如果您需要从字节切片解码结构,那么您应该使用encoding/binary
。创建此库是为了在字节序列和其他类型之间进行转换。
答案 1 :(得分:2)
union可能包含数字类型和八位字节字符串,因此我尝试使用字节切片作为值容器,并根据具体类型使用unsafe.Pointer
来访问它。
func test3() (total int64) {
type A struct {
t int32
u []byte
}
a := [...]A{{1, make([]byte, 8)}, {2, make([]byte, 8)}}
*(*int32)(unsafe.Pointer(&a[0].u)) = 100
*(*int16)(unsafe.Pointer(&a[1].u)) = 3
for i := 0; i < 5000000000; i++ {
p := &a[i%2]
switch p.t {
case 1:
total += int64(*(*int32)(unsafe.Pointer(&p.u)))
case 2:
total += int64(*(*int16)(unsafe.Pointer(&p.u)))
}
}
return
}
结果:
$ go run union.go
257500000000
12.844752701s
$ go run -compiler gccgo -gccgoflags -O3 union.go
257500000000
6.640667s
它是最好的版本吗?
答案 2 :(得分:2)
我敢打赌,要使其更接近C变体,这就是我得到的:
(full code)
https://play.golang.org/p/3FJTI6xSsd8
事情是,我们遍历所有struct的字段并将它们重定向到缓冲区的存储(出于内存节省和通用性的考虑,它在编译时从模板struct引用)
result:
func test() (total int64) {
type A struct {
t int32
u struct {
// embedded buffer of union
FooSize
// mark all types inside as pointer types
i *int32 // long
v *int16 //short
}
}
var a [2]A
// initialize them
Union(&a[0].u)
Union(&a[1].u)
a[0].t = 1
*a[0].u.i = 100
a[1].t = 2
*a[1].u.v = 3
for c := 0; c < 5000000000; c++ {
p := &a[c%2]
switch p.t {
case 1:
total += int64(*p.u.i)
case 2:
total += int64(*p.u.v)
}
}
return
}
//您的板凳:
257500000000
8.111239763s
//本机工作台(8,18800064s):
BenchmarkUnion 1 8188000640 ns/op 80 B/op 1 allocs/op
将其投放到5美元的Digitalocean小滴上。
实施被诅咒,可能与Go的未来版本(当前为1.13)不兼容,但是用法(由于行为)类似于C,还支持任何类型(您也可以用结构替换整数)
答案 3 :(得分:0)
我编写了一个小工具来生成名为 unionize
的 C 风格联合,您可以在 https://github.com/zyedidia/unionize 找到它。你给它一个模板,然后它会生成像联合一样的 Go 代码,并且具有与 C 相当的性能(警告:它使用 unsafe 包,请参阅 github 存储库了解其工作原理以及对替代方案的详细讨论)。>
我使用 unionize 将您的 C 基准测试复制到 Go 中。首先为联合创建一个模板,例如在 union.go
:
package main
type Int struct {
i int32
v int16
}
现在使用 unionize
生成将进入 a_union.go
的实际联合代码:
$ unionize -output=a_union.go Int union.go
这会从 IntUnion
模板创建一个新类型 Int
,该模板公开了操作联合成员的函数。现在我们可以使用该类型编写基准测试:
package main
import "fmt"
type A struct {
t int
u IntUnion
}
func main() {
var a [2]A
a[0].t = 1
a[0].u.iPut(100)
a[1].t = 2
a[1].u.vPut(3)
var total int
for i := 0; i < 5000000000; i++ {
p := &a[i%2]
switch p.t {
case 1:
total += int(p.u.i())
case 2:
total += int(p.u.v())
}
}
fmt.Println(total)
}
当我计时的时候:
$ go build main.go a_union.go
$ time ./main
257500000000
real 0m6.202s
user 0m6.197s
sys 0m0.012s
还不错! (在我的机器上,C 基准测试运行大约需要 3 秒)。该工具相当小,如果您需要更多功能,或者您发现任何错误,请告诉我。