Function table vs switch in golang

I am writing a simple emulator in go (should I? Or should I go back to c?). Anyway, I get the instruction and decode it. at this moment i have a byte like 0x81 and i have to execute the correct function.

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 that

 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 is better?
2.which is faster?
also
3.can I declare an inline function?
4.i have a cpu struct in which I have registers, etc., Would it be faster if I had registers and everything is global? (without struct )

Thank you very much.

+6
source share
3 answers
  • The first version looks better to me, YMMV.

  • Mark it. Depends on how good the compiler optimizer is. The Jump Table version may be faster if the compiler is not trying to optimize hard enough.

  • Depends on your definition of what “declare an inline function” means. Go can only declare and define functions / methods at the top level. But functions are first-class citizens in Go, so you can have variables / parameters / return values ​​and structured function type types. In all these places, the function literal function can also be assigned to a variable / field / element ...

  • Maybe. However, I would suggest not storing the state of the processor in a global variable. As soon as you possibly decide to emulate multi-core processors, this will be welcome; -)

+2
source

I did some tests, and the version table is faster than the switch version when you have more than 4 cases.

I was surprised to find that the Go compiler (gc, at least not sure about gccgo) seems to be not so smart as to turn a dense switch into a transition table.

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

+15
source

If you have some kind of expression and you want to evaluate it for a large number of data lines, then you can only compile it into the lambdas tree only once and not calculate any switches at each iteration at all;

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

The compilation function (in a very crude pseudo-language) will look 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)} } } 

Thus, as a result, compilation returns func, which should be called on every line of your data, and there will be no switches or other calculations at all. The question remains about operations with data of different types, that is, for your own research;) This is an interesting approach, in situations where there is no mechanism for switching to the table: :) ​​but I'm sure func call is a more complicated operation, and then a jump.

0
source

Source: https://habr.com/ru/post/911952/


All Articles