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. 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 ;-)

其他提示

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.

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 ;) This is an interesting approach, in situations, when there is no jump-table mechanism available :) but sure, func call is more complex operation then jump.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top