Таблица функций против переключателя в Голанге

Я пишу простой эмулятор на ходу (я должен или я должен вернуться к с?). в любом случае, я беру инструкцию и декодирую ее. на данный момент у меня есть байт, как 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. у меня есть cpustruct в котором у меня есть регистры и т. д. было бы быстрее, если бы у меня были регистры и все в качестве глобалов? (без struct)

большое спасибо вам.

3 ответа

Решение
  1. Первая версия выглядит лучше для меня, YMMV.

  2. Оцените это. Зависит от того, насколько хорош компилятор при оптимизации. Версия "таблицы переходов" может быть быстрее, если компилятор не слишком старается оптимизировать.

  3. Зависит от вашего определения, что такое "объявлять встроенную функцию". Go может объявлять и определять функции / методы только на верхнем уровне. Но функции являются гражданами первого класса в Go, поэтому можно иметь переменные / параметры / возвращаемые значения и структурированные типы типов функций. Во всех этих местах функциональный литерал может [также] быть назначен переменной / полю / элементу...

  4. Возможно. Тем не менее, я бы предложил не сохранять состояние процессора в глобальной переменной. Как только вы решите эмулировать многоядерный процессор, это будет приветствоваться;-)

Я сделал несколько тестов, и настольная версия быстрее, чем версия коммутатора, если у вас более 4 случаев.

Я был удивлен, обнаружив, что компилятор Go (в любом случае gc; не уверен насчет gccgo) не настолько умен, чтобы превратить плотный переключатель в таблицу переходов.

Обновление: Кен Томпсон разместил в списке рассылки Go описание трудностей, связанных с оптимизацией коммутатора.

Если у вас есть немного выражения, и вы хотите оценить его для большого количества строк данных, то вы можете только один раз скомпилировать его в дерево лямбда-выражений и вообще не вычислять какие-либо переключатели на каждой итерации;

Например, учитывая такие аст: {* (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 является более сложной операцией, чем Прыгать.

Другие вопросы по тегам