таблица функций vs переключатель в golang
im пишу простой эмулятор в go (должен ли я? или мне вернуться в Си?). так или иначе, я забираю инструкцию и расшифровываю ее. на данный момент у меня есть байт, как 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.я могу объявить функцию как inline?
4.у меня есть cpu struct, в котором у меня есть регистры и т. д. было бы быстрее, если бы у меня были регистры и все как глобалы? (без struct)
большое спасибо.
3 ответов
- первая версия выглядит лучше для меня, YMMV. 
- критерии. Зависит от того, насколько хорош компилятор при оптимизации. Версия "jump table" может быть быстрее, если компилятор недостаточно старается оптимизировать. 
- зависит от вашего определения того, что такое "объявить функцию inline". Go может объявлять и определять функции / методы только на верхнем уровне. Но функции-граждане первого класса в Go, поэтому можно иметь переменные / параметры / возвращаемые значения и структурированные типы типов функций. Во всех этих местах функция literal можно [также] назначить переменной/полю/элементу... 
- возможно. Тем не менее я бы предложил не сохранять состояние cpu в глобальной переменной. Как только вы, возможно, решите эмулировать multicore, это будет приветствоваться ; -) 
Я сделал несколько тестов, и версия таблицы быстрее, чем версия коммутатора, как только у вас будет более 4 случаев.
Я был удивлен, обнаружив, что компилятор Go (gc, во всяком случае, не уверен в gccgo), похоже, недостаточно умен, чтобы превратить плотный коммутатор в таблицу перехода.
обновление: Кен Томпсон опубликовал в списке рассылки Go описание трудности оптимизации переключатель.
Если у вас есть ast некоторого выражения, и вы хотите его оценить для большого количества строк данных, то вы можете только один раз скомпилировать его в дерево лямбд и вообще не вычислять никаких переключателей на каждой итерации;
например, учитывая такой 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)} 
    }
}
поэтому в конечном итоге compile возвращает func, который должен вызываться в каждой строке ваших данных и там не будет никаких переключателей или других вычислений вообще. Остается вопрос об операциях с данными разных типов, то есть для собственного исследования ;) Это интересный подход, в ситуациях, когда нет механизма перехода таблицы доступны :) но конечно, вызов func является более сложной операцией, чем прыжок.
