6

我正在用 go 编写一个简单的模拟器(我应该吗?或者我应该回到 c 吗?)。无论如何,我正在获取指令并对其进行解码。此时我有一个像 0x81 这样的字节,我必须执行正确的函数。

我应该有这样的东西吗

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 fnTable[opcode](sys)
}

1.哪个更好?
2.哪个更快?
3.我可以
声明一个内联函数吗?
4.我有一个cpu struct我有寄存器等。如果我有寄存器并且全部作为全局变量会更快吗?(没有struct

非常感谢您。

4

3 回答 3

16

我做了一些基准测试,一旦你有超过 4 个案例,table 版本比 switch 版本更快。

我惊讶地发现 Go 编译器(无论如何是 gc;不确定 gccgo)似乎不够聪明,无法将密集开关变成跳转表。

更新:Ken Thompson 在 Go 邮件列表上发布了描述优化 switch 的困难

于 2012-03-30T02:19:10.400 回答
3
  1. 第一个版本对我来说看起来更好,YMMV。

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

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

  4. 可能。我仍然建议不要将 cpu 状态保存在全局变量中。一旦您可能决定去模拟多核,它将受到欢迎;-)

于 2012-03-29T15:35:31.963 回答
0

如果你有某个表达式的 ast,并且你想对大量数据行进行评估,那么你可能只将它编译到 lambdas 树中,并且根本不计算每次迭代的任何开关;

例如,给定这样的 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)} 
    }
}

所以最终编译返回函数,必须在数据的每一行上调用它,并且根本没有开关或其他计算内容。仍然存在关于使用不同类型数据的操作的问题,这是您自己的研究;)这是一种有趣的方法,在没有可用跳转表机制的情况下:) 但可以肯定的是,func 调用是更复杂的操作跳。

于 2016-05-13T12:17:29.813 回答