таблица функций и переключатель в Голанге - PullRequest
6 голосов
/ 29 марта 2012

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

Большое спасибо.

Ответы [ 3 ]

15 голосов
/ 30 марта 2012

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

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

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

2 голосов
/ 29 марта 2012
  1. Первая версия выглядит лучше для меня, YMMV.

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

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

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

0 голосов
/ 13 мая 2016

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

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

...