函数表与switch在golang中的比较

huangapple go评论81阅读模式
英文:

table of functions vs switch in golang

问题

func (sys *cpu) eval() {
switch opcode {
case 0x80:
sys.add(sys.b)
case 0x81:
sys.add(sys.c)
etc
}
}

var fnTable = []func(*cpu) {
0x80: func(sys *cpu) {
sys.add(sys.b)
},
0x81: func(sys *cpu) {
sys.add(sys.c)
}
}
func (sys *cpu) eval() {
return fnTableopcode
}

1.哪一个更好?
2.哪一个更快?
还有
3.我可以内联声明一个函数吗?
4.我有一个cpu结构体,其中包含寄存器等。如果我将寄存器和所有内容作为全局变量,会更快吗?(不使用结构体)

非常感谢。

英文:

im am writing a simple emulator in go (should i? or should i go back to c?).
anyway, i am fetching the instruction and decoding it. at this point i have a byte like 0x81, and i have to execute the right function.

should i have something like this

func (sys *cpu) eval() {
	switch opcode {
	case 0x80:
		sys.add(sys.b)
	case 0x81:
		sys.add(sys.c)
	etc
	}
}

or something like this

var fnTable = []func(*cpu) {
	0x80: func(sys *cpu) {
		sys.add(sys.b)
	},
	0x81: func(sys *cpu) {
		sys.add(sys.c)
	}
}
func (sys *cpu) eval() {
	return fnTable[opcode](sys)
}

1.which one is better?
2.which one is faster?
also
3.can i declare a function inline?
4.i have a cpu struct in which i have the registers etc. would it be faster if i have the registers and all as globals? (without the struct)

thank you very much.

答案1

得分: 18

我做了一些基准测试,发现当你有超过大约4个case时,表格版本比开关版本更快。

我很惊讶地发现Go编译器(至少是gc,不确定gccgo)似乎不够聪明,不能将密集的开关转换为跳转表。

更新
Ken Thompson在Go邮件列表上发表了一篇描述优化开关的困难的帖子。

英文:

I did some benchmarks and the table version is faster than the switch version once you have more than about 4 cases.

I was surprised to discover that the Go compiler (gc, anyway; not sure about gccgo) doesn't seem to be smart enough to turn a dense switch into a jump table.

Update:
Ken Thompson posted on the Go mailing list describing the difficulties of optimizing switch.

答案2

得分: 3

  1. 第一个版本对我来说看起来更好,但可能因人而异。

  2. 进行基准测试。这取决于编译器在优化方面的能力。如果编译器没有尽力优化,那么“跳转表”版本可能会更快。

  3. 这取决于你对“声明内联函数”的定义。Go语言只能在顶层声明和定义函数/方法。但是在Go语言中,函数是一等公民,所以可以有函数类型的变量/参数/返回值和结构化类型。在所有这些地方,都可以将函数字面量分配给变量/字段/元素...

  4. 可能。不过我建议不要将CPU状态保存在全局变量中。一旦你决定模拟多核,它将会很受欢迎 函数表与switch在golang中的比较

英文:
  1. The first version looks better to me, YMMV.

  2. Benchmark it. Depends how good is the compiler at optimizing. The "jump table" version might be faster if the compiler doesn't try hard enough to optimize.

  3. Depends on your definition of what is "to declare function inline". Go can declare and define functions/methods at the top level only. But functions are first class citizens in Go, so one can have variables/parameters/return values and structured types of function type. In all this places a function literal can [also] be assigned to the variable/field/element...

  4. Possibly. Still I would suggest to not keep the cpu state in a global variable. Once you possibly decide to go emulating multicore, it will be welcome 函数表与switch在golang中的比较

答案3

得分: 0

如果您有某个表达式的AST,并且想要对大量数据行进行评估,那么您只需要将其编译成lambda树,并且在每次迭代中不进行任何开关计算;

例如,给定这样的AST:{* (a, {+ (b, c)})}

编译函数(用非常粗糙的伪代码语言)可能如下所示:

func (e *evaluator) compile(brunch ast) {
    switch brunch.type {
    case binaryOperator:
        switch brunch.op {
        case *: return func() {compile(brunch.arg0) * compile(brunch.arg1)}
        case +: return func() {compile(brunch.arg0) + compile(brunch.arg1)}
        }
    case BasicLit: return func() {return brunch.arg0}
    case Ident: return func(){return e.GetIdent(brunch.arg0)} 
    }
}

因此,最终编译返回一个函数,必须在数据的每一行上调用该函数,而且根本不会有开关或其他计算内容。
关于不同类型数据的操作问题仍然存在,这需要您自己进行研究;)
这是一种有趣的方法,在没有跳转表机制可用的情况下:) 但是,确实,函数调用比跳转更复杂。

英文:

If you have the ast of some expression, and you want to eval it for a big amount of data rows, then you may only once compile it into the tree of lambdas, and do not calculate any switches on each iteration at all;

For example, given such ast: {* (a, {+ (b, c)})}

Compile function (in very rough pseudo language) will be something like this:

func (e *evaluator) compile(brunch ast) {
    switch brunch.type {
    case binaryOperator:
        switch brunch.op {
        case *: return func() {compile(brunch.arg0) * compile(brunch.arg1)}
        case +: return func() {compile(brunch.arg0) + compile(brunch.arg1)}
        }
    case BasicLit: return func() {return brunch.arg0}
    case Ident: return func(){return e.GetIdent(brunch.arg0)} 
    }
}

So eventually compile returns the func, that must be called on each row of your data and there will be no switches or other calculation stuff at all.
There remains the question about operations with data of different types, that is for your own research 函数表与switch在golang中的比较
This is an interesting approach, in situations, when there is no jump-table mechanism available 函数表与switch在golang中的比较 but sure, func call is more complex operation then jump.

huangapple
  • 本文由 发表于 2012年3月29日 23:10:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/9928221.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定