golang中的函数与开关表

时间:2012-03-29 15:10:41

标签: emulation go gameboy

我正在写一个简单的模拟器(我应该?还是应该回到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.哪一个更好?
哪一个更快?

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

非常感谢你。

3 个答案:

答案 0 :(得分:15)

我做了一些基准测试,一旦你有超过4个案例,表格版本比交换机版本更快。

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

<强>更新: Ken Thompson在Go邮件列表上发布了the difficulties of optimizing switch

答案 1 :(得分:2)

  1. 对我来说第一个版本看起来更好,YMMV。

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

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

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

答案 2 :(得分:0)

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

例如,给出这样的:{* (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,必须在数据的每一行调用func,并且根本不会有任何开关或其他计算内容。 关于不同类型数据的操作仍有问题,这是您自己的研究;) 这是一种有趣的方法,在没有跳转表机制可用的情况下:)但是肯定,func调用操作更复杂然后跳转。