Оптимизация для интерпретатора brainfuck

в качестве упражнения, чтобы помочь мне узнать об интерпретаторах и оптимизации, ни о которых я ничего не знаю, я написал интерпретатор brainfuck в C. Он, похоже, работает безупречно до сих пор, хотя он не конкурирует хорошо в скорости выполнения по сравнению с другими быстрыми интерпретаторами.

каким образом я могу изменить этот переводчик для повышения производительности (или иначе)?

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

struct { long instruction; long loop; }

на loop значение-это индекс соответствия ] инструкция, если инструкция [, и индекс соответствия [ инструкция, если инструкция ], что позволяет быстро скакать. Я бы предположил, что этот процесс "разбора" (который не занимает много времени) улучшает время выполнения по сравнению с избыточным повторным разбором, чтобы найти соответствующий квадрат скобки каждый раз, когда они необходимы.

интересным тестом скорости интерпретатора brainfuck является эта программа:

++++++++[->-[->-[->-[-]<]<]<]>++++++++[<++++++++++>-]<[>+>+<<-]>-.>-----.>
  1. первая версия переводчика
  2. переводчик после реализации ответ Джерри Коффина, который удаляет гигантский переключатель в цикле выполнения, сделав instruction структуры instruction прямой указатель на функцию операции - это работает медленнее, чем предыдущая версия (функция вызова накладных расходов?)
  3. переводчик после отмена предыдущего изменения и добавление оптимизации для "сворачивания" нескольких последовательных операций без цикла, сокращение циклов цикла -это работает немного быстрее, чем оригинал

7 ответов


Я вижу несколько возможностей. Я думаю, что я бы пошел, чтобы превратить его в компилятор, который произвел код с прямым потоком. То есть, когда вы читаете ввод, вместо копирования большинства "инструкций" в память более или менее как есть, я бы вместо этого написал код для реализации каждой инструкции как функции и скопировал указатель на каждую функцию в память. Затем выполнение кода будет состоять из вызова этих функций по порядку. Вероятно, эта функция вернет индекс (или возможно, адрес) следующей инструкции для выполнения, поэтому вы получите что-то вроде:

typedef int return_type;
typedef return_type (*f)(void);

f *im = malloc(sizeof(f) * ia);

ci = (*(im[ci]))();

у меня также было бы три отдельных функции для каждой инструкции, по одной для каждого режима BF_END_*, поэтому вам придется иметь дело только с этим на этапе "компиляции". При выполнении кода у вас будет указатель непосредственно на правильную функцию.

Edit:

Я немного поиграл с кодом. Я разделил адреса цикла на отдельный массив, и слил большую часть разбора вместе, так что выглядит это так:

for (ii = 0; (i = getc(fp)) != EOF; ++ii) {
    if (++in > ia) {
        ia *= 2;
        im = realloc(im, sizeof(*im) * ia);
        loops = realloc(loops, sizeof(*loops) * ia);
    }
    im[in-1] = i;
    switch (i) {
        case BF_OP_LSTART:
            if (ln >= la)
                ls = realloc(ls, sizeof(*ls) * (la *= 2));
            ls[ln++] = ii;
            break;
        case BF_OP_LEND:
            loops[in-1] = ls[--ln];
            loops[ls[ln]] = ii;
            break;
    }
}

Это не делает реальной разницы в скорости, но делает код намного короче, и (по крайней мере на мой взгляд) легче понять.

Edit2:

хорошо, у меня был шанс поиграть с этим немного больше, и нашел одну (довольно странную) оптимизацию, которая, похоже, помогает хотя бы немного. Компиляторы часто производят незначительно лучший код для операторов switch с плотным регистром значения, поэтому я попытался преобразовать в это и получил улучшение около 9-10% (в зависимости от компилятора).

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define BF_END_ERROR    'e'
#define BF_END_IGNORE   'i'
#define BF_END_WRAP     'w'
#define BF_OP_VINC      '+'
#define BF_OP_VDEC      '-'
#define BF_OP_PINC      '>'
#define BF_OP_PDEC      '<'
#define BF_OP_LSTART    '['
#define BF_OP_LEND      ']'
#define BF_OP_IN        ','
#define BF_OP_OUT       '.'

enum { 
    C_OP_VINC, 
    C_OP_VDEC, 
    C_OP_PINC, 
    C_OP_PDEC, 
    C_OP_LSTART, 
    C_OP_LEND, 
    C_OP_IN, 
    C_OP_OUT 
};

typedef struct {
    long instruction;       /* instruction type */
    long loop;              /* 'other' instruction index in a loop */
} instruction;
void die(const char *s, ...) {
    va_list a;
    va_start(a, s);
    fprintf(stderr, "brief: error: ");
    vfprintf(stderr, s, a);
    putchar(10);
    va_end(a);
    exit(1);
}
int main(int argc, char **argv) {
    unsigned instruction_count = 0;
    long
        ci = 0,             /* current cell index */
        cn = 4096,          /* number of cells to allocate */
        cw = BF_END_WRAP,   /* cell wrap behaviour */
        ia = 4096,          /* number of allocated instructions */
        ii = 0,             /* current instruction index */
        in = 0,             /* number of used instructions */
        la = 4096,          /* loop stack allocation */
        ln = 0,             /* loop stack used */
        va = 0,             /* minimum value */
        vb = 255,           /* maximum value */
        vw = BF_END_WRAP    /* value wrap behaviour */
    ;
    instruction *im = malloc(sizeof(instruction) * ia); /* instruction memory */
    long *cm = NULL;        /* cell memory */
    long *ls = malloc(sizeof(long) * la);               /* loop stack */
    FILE *fp = NULL;
    int i;
    while ((i = getopt(argc, argv, "a:b:c:f:hv:w:")) != -1) {
        switch (i) {
            case 'a': va = atol(optarg); break;
            case 'b': vb = atol(optarg); break;
            case 'c': cn = atol(optarg); break;
            case 'f':
                fp = fopen(optarg, "r");
                if (!fp)
                    die("%s: %s", optarg, strerror(errno));
                break;
            case 'h':
                fputs(
                    "brief: a flexible brainfuck interpreter\n"
                    "usage: brief [options]\n\n"
                    "options:\n"
                    "   -a  set minimum cell value (default 0)\n"
                    "   -b  set maximum cell value (default 255)\n"
                    "   -c  set cells to allocate (default 4096)\n"
                    "   -f  source file name (required)\n"
                    "   -h  this help output\n"
                    "   -v  value over/underflow behaviour\n"
                    "   -w  cell pointer over/underflow behaviour\n\n"
                , stderr);
                fputs(
                    "cells are 'long int' values, so do not use -a with a "
                    "value less than -2^31 or -2^63, and do not use -b with a "
                    "value more than 2^31-1 or 2^63-1, depending on your "
                    "architecture's 'long int' size.\n\n"
                    "over/underflow behaviours can be one of:\n"
                    "   e   throw an error and quit upon over/underflow\n"
                    "   i   do nothing when attempting to over/underflow\n"
                    "   w   wrap-around to other end upon over/underflow\n"
                , stderr);
                exit(1);
                break;
            case 'v': vw = optarg[0]; break;
            case 'w': cw = optarg[0]; break;
            default: break;
        }
    }
    if (!fp)
        die("no source file specified; use -f");
    for (ii = 0; (i = getc(fp)) != EOF; ++ii) {
        if (++in > ia) {
            ia *= 2;
            im = realloc(im, sizeof(*im) * ia);
        }
        switch (i) {
            case BF_OP_LSTART:
                if (ln >= la)
                    ls = realloc(ls, sizeof(*ls) * (la *= 2));
                ls[ln++] = ii;
                im[in-1].instruction = C_OP_LSTART;
                break;
            case BF_OP_LEND:
                im[in-1].loop = ls[--ln];
                im[ls[ln]].loop = ii;
                im[in-1].instruction = C_OP_LEND;
                break;
            case BF_OP_VINC:
                im[in-1].instruction = C_OP_VINC;
                break;
            case BF_OP_VDEC:
                im[in-1].instruction = C_OP_VDEC;
                break;
            case BF_OP_PINC:
                im[in-1].instruction = C_OP_PINC;
                break;
            case BF_OP_PDEC:
                im[in-1].instruction = C_OP_PDEC;
                break;
            case BF_OP_IN:
                im[in-1].instruction = C_OP_IN;
                break;
            case BF_OP_OUT:
                im[in-1].instruction = C_OP_OUT;
                break;
        }
    }
    cm = memset(malloc(cn * sizeof(long)), 0, cn * sizeof(long));
    for (ii = 0; ii < in; ii++) {
        ++instruction_count;
        switch (im[ii].instruction) {
            case C_OP_VINC:
                if (cm[ci] == vb)
                    switch (vw) {
                        case BF_END_ERROR:
                            die("value overflow");
                            break;
                        case BF_END_IGNORE: break;
                        case BF_END_WRAP: cm[ci] = 0; break;
                    }
                else ++cm[ci];
                break;
            case C_OP_VDEC:
                if (cm[ci] == 0)
                    switch (vw) {
                        case BF_END_ERROR:
                            die("value underflow");
                            break;
                        case BF_END_IGNORE: break;
                        case BF_END_WRAP: cm[ci] = vb; break;
                    }
                else --cm[ci];
                break;
            case C_OP_PINC:
                if (ci == cn - 1)
                    switch (cw) {
                        case BF_END_ERROR:
                            die("cell index overflow");
                            break;
                        case BF_END_IGNORE: break;
                        case BF_END_WRAP: ci = 0; break;
                    }
                else ++ci;
                break;
            case C_OP_PDEC:
                if (ci == 0)
                    switch (cw) {
                        case BF_END_ERROR:
                            die("cell index underflow");
                            break;
                        case BF_END_IGNORE: break;
                        case BF_END_WRAP: ci = cn - 1; break;
                    }
                else --ci;
                break;
            case C_OP_IN:
                cm[ci] = getchar();
                break;
            case C_OP_OUT:
                putchar(cm[ci]);
                break;
            case C_OP_LSTART:
                if (!cm[ci])
                    ii = im[ii].loop;
                break;
            case C_OP_LEND:
                if (cm[ci])
                    ii = im[ii].loop;
                break;
            default: break;
        }
    }
    fprintf(stderr, "Executed %d instructions\n", instruction_count);
    free(cm);
    return 0;
}

Ну, это не С. И это не является переводчиком. Так что, да, это совершенно неуместный вопрос.

но что это такое, это идеально портативный компилятор brainfuck, использующий C++0x variadic шаблоны. Вы должны #define PROGRAM как последовательность символов c-синтаксиса, разделенных запятыми, потому что я не мог извлечь их из строки во время компиляции. Но в остальном это законно. Я думаю.

протестировано с g++ 4.5.2, используя g++ -std=c++0x -O2 -Wall.

#include <cstdio>
#include <vector>

#define PROGRAM '+', '+', '+', '+', '+', '+', '+', '+', '[', '-', '>',  \
        '-', '[', '-', '>', '-', '[', '-', '>', '-', '[', '-', ']', '<', \
        ']', '<', ']', '<', ']', '>', '+', '+', '+', '+', '+', '+', '+', \
        '+', '[', '<', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', \
        '>', '-', ']', '<', '[', '>', '+', '>', '+', '<', '<', '-', ']', \
        '>', '-', '.', '>', '-', '-', '-', '-', '-', '.', '>'

template<char... all>
struct C;

template<char... rest>
struct C<'>', rest...> {
    typedef C<rest...> rest_t;
    typedef typename rest_t::remainder remainder;
    static char *body(char *p) {
        return rest_t::body(p+1);
    }
};

template<char... rest>
struct C<'<', rest...> {
    typedef C<rest...> rest_t;
    typedef typename rest_t::remainder remainder;
    static char *body(char *p) {
        return rest_t::body(p-1);
    }
};

template<char... rest>
struct C<'+', rest...> {
    typedef C<rest...> rest_t;
    typedef typename rest_t::remainder remainder;
    static char *body(char *p) {
        ++*p;
        return rest_t::body(p);
    }
};


template<char... rest>
struct C<'-', rest...> {
    typedef C<rest...> rest_t;
    typedef typename rest_t::remainder remainder;
    static char *body(char *p) {
        --*p;
        return rest_t::body(p);
    }
};

template<char... rest>
struct C<'.', rest...> {
    typedef C<rest...> rest_t;
    typedef typename rest_t::remainder remainder;
    static char *body(char *p) {
        putchar(*p);
        return rest_t::body(p);
    }
};

template<char... rest>
struct C<',', rest...> {
    typedef C<rest...> rest_t;
    typedef typename rest_t::remainder remainder;
    static char *body(char *p) {
        *p = getchar();
        return rest_t::body(p);
    }
};

template<char... rest>
struct C<'[', rest...> {
    typedef C<rest...> rest_t;
    typedef typename rest_t::remainder::remainder remainder;
    static char *body(char *p) {
        while (*p) {
            p = rest_t::body(p);
        }
        return rest_t::remainder::body(p);
    }
};


template<char... rest>
struct C<']', rest...> {
    typedef C<rest...> rest_t;
    struct remainder_hack {
        typedef typename rest_t::remainder remainder;
        static char *body(char *p) {
            return rest_t::body(p);
        }
    };
    typedef remainder_hack remainder;
    static char *body(char *p) {
        return p;
    }
};

template<>
struct C<> {
    static char *body(char *p) {
        return p;
    }
    struct remainder {
        static char *body(char *p) {
            return p;
        }
    };
};

int
main(int argc, char *argv[])
{
    std::vector<char> v(30000, 0);
    C<PROGRAM> thing;

    thing.body(&v[0]);
    return 0;
}

поскольку весь смысл этого проекта заключается в обучении, использование инструмента или заменяющего решения явно не отвечает на вопрос.

во-первых, отказ от ответственности: я не программист x86-я сделал приличный объем работы во встроенных средах и теперь (с мобильными телефонами) чипы ARM. Перейдем к хорошему...

есть два основных способа сделать ваш интерпретатор быстрее: Сделайте это оптимизировать сам код BF, и сделать сам интерпретатор оптимизирован. Я рекомендую понемногу в один шаг ниже.

насколько я знаю, x86 тратит много пространства краски на обеспечение относительно впечатляющих быстрых ветвящихся свойств. Вероятно, из-за этого я видел, как несколько компиляторов (включая gcc) создают вложенные ветви в пользу фактических таблиц перехода для x86. Таблицы перехода звучат привлекательно в теории, но на самом деле x86 оптимизирует так хорошо для устаревших методов, что обычное мышление big-O просто не применяется в большинстве случаев. Вот почему долгосрочные разработчики x86 расскажут вы, если вы хотите рассчитать, как быстро код, то вам нужно написать его, запустить его и время его.

несмотря на скорость, с которой ветви могут возникать на x86, по-прежнему, вероятно, стоит потратить немного накладных расходов на не ветвление. Поскольку инструкции BF так просты в любом случае, это может принять форму "сделать большинство инструкций сразу, так как это быстрее, чем другая ветвь."Некоторые из них могут даже выполняться параллельно процессором, где ветвление не может быть. (x86 имеет достаточно декодирования и единицы исполнения в одном ядре такие, что это, вероятно, возможно)

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

кроме того, ваша программа очень универсальный. Это позволит вам использовать любое максимальное значение для упаковки. Если бы это была сила двух, вы бы просто выполняли побитовое и (очень быстро поскольку это один цикл процессора практически на всех современных платформах), эквивалентный обертыванию вместо сравнения и ветвления. Дьявол в деталях для написания действительно быстрых интерпретаторов - каждый маленький бит, который вы добавляете, делает его намного сложнее.

с этой целью я рекомендую оптимизировать то, что делает ваш интерпретатор BF (например, оберните его в степени два для значений). Побитовое и трюк в одиночку и заставляя обернуть быть вариантом (как это имеет место в большинстве современных программ языки для переполнений/ underflows) уже удаляет по крайней мере две ветви.

Как только о них позаботятся, это позволяет интересную оптимизацию: выбросьте инструкции BF и вместо этого сделайте свою машину выполнять различные инструкции, которые более подходят для интерпретатора.

рассмотрим Java: Java не интерпретирует. Он JITs в совершенно разных языка.

Я рекомендую применять ту же логику. Вы уже сделали это немного, давая инструкция значение, связанное с ними.

Я рекомендую создать инструкцию со следующей информацией:

  • сумма, чтобы добавить к значению at указатель данных (или вычесть -- вычитание просто добавляет отрицательное число)
  • сумма, чтобы добавить к значению of указатель данных (или вычесть)
  • указатель на следующую инструкцию для выполнения
  • значение по сравнению со значением в ячейке до он изменен

цикл интерпретатора затем изменяется на логику следующим образом: добавьте значение данных в инструкции к значению в указателе данных добавить данные адрес значение в инструкции к самому указателю данных если значение в ячейке совпадает с нашим значением сравнения установите указатель инструкции на новый указатель инструкции продолжить (к следующему циклу интерпретатора) если значение сравнения является специальным значением (например, вы можете выбрать 0x80000000) ручка ввода/вывода питания увеличить адрес инструкции на единицу

первоначальная интерпретация становится сложнее: Инструкции теперь могут объединять +, -,, а иногда даже [, и обычно ] в одну и ту же инструкцию. Первая ветвь в цикле может быть представлена без ветвления, если вы умны в этом (и даже больше эффективно с некоторой сборкой, застрявшей в или некоторых встроенных компиляторах). Возможно, вы захотите сообщить компилятору, что вторая ветвь вряд ли попадет (в этом случае узким местом является ввод/вывод, а не скорость интерпретации, поэтому, даже если вы делаете много ввода/вывода, одна маленькая неоптимизированная ветвь не будет иметь значения).

необходимо позаботиться Для условия конц -- хода. Последняя инструкция в вашей интерпретируемой программе BF теперь всегда должна делать инструкцию указатель NULL, поэтому цикл завершается.

порядок, в котором происходит интерпретация, важен, потому что -/+ выполняется до выполняется до [ выполняется до ] выполняется до ввода-вывода.Итак, > + - это две интерпретируемые инструкции, а + > - одна.

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

Я надеюсь, что я дал вам идею попробовать и проверить, что приводит вас к более шагам оптимизации. Я не пробовал ничего из этого сам, так что это все еще гипотеза, основанная на прошлом опыте. Однако, даже если это не будет быстрее, вы получите некоторый опыт в переписывании кода BF на относительно другую архитектуру от интерпретатора vanilla BF.

П. С. большой вопрос!


Brainfuck должен быть довольно легко скомпилирован в код C, который затем компилируется и выполняется. Это, вероятно, будет очень быстрый Bf "переводчик".

принципиально все, что вам нужно сделать, это создать довольно тривиальный код для каждого оператора brainfuck слева направо в программе. Можно легко оптимизировать последовательности + и -; аналогично, можно оптимизировать последовательности , кэшируя подсчеты каждого из последних встречающийся. Это своего рода глазок оптимизация.

вот компилятор проекта, принимающий код BF в командной строке и печать скомпилированной программы на консоль:

int increments;  // holds pending increment operations

void flush_increments(){
   if (increments==0) return;
   printf("  *ptr+=%d;\n",increments);
   increments=0;
}

int steps; // holds pending pointer steps

void flush_steps(){
   if (steps==0) return;
   printf("  ptr+=%d;\n",steps);
   steps=0;
}

int main(int argc, char **argv){
    // Brainfuck compiler
    if( !(argc > 1) )
        return 1;
    unsigned char *code = argv[1];
    int nesting=0;
    printf("int main(){\n");
    printf("  #define CELLSPACE 1000\n");
    printf("  unsigned char *ptr = malloc(sizeof(char)*CELLSPACE);\n");
    printf("  if(ptr == NULL) return 1;\n")
    printf("  for(int i=0;i<CELLSPACED;i++) ptr[i]=0; // reset cell space to zeros");
    increments=0;
    steps=0;
    for(;;) {
        switch(*code++) {
            case '+':
                flush_steps();
                ++increments;
                break;
            case '-':
                flush_steps();
                --increments;
                break;
            case '>':
                flush_increments();
                ++steps;
                break;
            case '<':
                flush_increments();
                --steps;
                break;
            case '[':
                flush_increments();
                flush_steps();
                printf("while(*ptr){");
                ++nesting;
                break;
            case ']': 
                flush_increments();
                flush_steps();
                if (--nesting<0)
                   { printf("Unmatched ']'\n");
                     return 1;
                   }
                printf("}\n";);
                break;
            case '.':
                flush_increments();
                flush_steps();
                printf(" putc(*ptr, stdout);\n");
                break;
            case ',':
                increments=0;
                flush_steps();
                printf("*ptr = getc(stdin);");
                break;
            case '':
                 printf("}");
                 if (nesting>0)
                   { printf("Unmatched '['\n");
                     return 1;
                   }
                return 0;
        }
    }
}

Это набирается с верхней части моей головы, вдохновленный кодом Мэтью Бланчарда (спасибо Мэтью!), но не проверял. Я оставлю это какой-нибудь другой душе; не стесняйтесь исправлять код если обнаружите проблему. Очевидно, было бы лучше, если бы он написал свой код в файл : -}

[я использовал http://en.wikipedia.org/wiki/Brainfuck статья как очевидное вдохновение для создания кода].

программа BF OP:

++++++++[->-[->-[->-[-]++++++++[-]+>+-.>-----.>

следует скомпилировать в (добавлен отступ):

 int main(){
 #define CELLSPACE 1000
   unsigned char *ptr = malloc(sizeof(char)*CELLSPACE);
   if(ptr == NULL) return 1;
   for(int i=0;i<CELLSPACED;i++) ptr[i]=0; // reset cell space to zeros
   *ptr+=8;
   while(*ptr) {
      *ptr+=-1;
      ptr+=1;
      *ptr+=-1;
     while(*ptr) {
       *ptr+=-1;
       ptr+=1;
       *ptr+=-1;
       while(*ptr) {
         *ptr+=-1;
         ptr+=1;
         *ptr+=-1;
         while(*ptr) {
            *ptr+=-1;
         }
         ptr+=-1;
       }
       ptr+=-1;
     }
     ptr+=1;
     *ptr+=8;
     while (*ptr) {
        ptr+=-1;
        *ptr+=10;
        ptr+=1;
        *ptr+=-1;
     }
     ptr+=-1;
     while (*ptr) {
        ptr+=1;
        *ptr+=1;
        ptr+=1;
        *ptr+=1;
        ptr+=-2;
        *ptr+=-1;
     }
     ptr+=1;
     *ptr+=-1;
     putc(*ptr,stdout);
     ptr+=1;
     *ptr+=-5;
     putc(*ptr,stdout);
     ptr+=1;
 }

это, вероятно, в среднем довольно близко к одной машинной инструкции на Bf op.

кто-то, кто был действительно амбициозен, вычислит возможные значения для запись ptr на каждый пункт программы, я думаю, во многих случаях это относится к постоянной ячейки. Тогда можно было бы избежать косвенного доступа.

Если вы действительно хотели сходить с ума, вы могли бы выяснить, что делают команды BF до первого входного запроса; это должна быть "постоянная" начальная конфигурация памяти, и генерировать intializer CELLSPACE с этой константой и генерировать код для остальной части программы, как я показал. Если вы это сделали, пример OP программа исчезнет в один cellspace initialzer и несколько вызовов putc.


используйте инфраструктуру LLVM JIT и оптимизируйте код для вас...

edit: на самом деле это уже было сделано несколько раз; компилятор:http://www.remcobloemen.nl/2010/02/brainfuck-using-llvm/ JIT:https://github.com/resistor/BrainFTracing (Обратите внимание, как "компилятор" имеет длину 230 строк, считая также пустые строки, комментарии и #includes)

edit2: для downvoter: поскольку вы, похоже, пропустили его, значение моего ответ был "не изобретай колесо"


Я бы увидел несколько вещей.

код switch довольно сложно из-за обработки ошибок. Попробуйте реорганизовать то, что у вас есть только быстрый путь внутри коммутатора, и вызовите одну или несколько функций для ошибок. В общем, короче, вы получаете код внутри switch и чем меньше переменных вы используете, тем лучше ваш оптимизатор сможет окочуриться.

у вас слишком много косвенных ответов. Например, ваш индекс ci не служить. Иметь указатель, указывающий на фактическую ячейку. Это позволяет сохранить регистр. Вы можете сделать подобное с ii. Вместо того, чтобы сохранять номер инструкции, просто наведите указатель на позицию в cm.

в любом случае, проверьте ассемблер, созданный. Вы быстро увидите, где ваш компилятор производит слишком много разлива регистров или тому подобное.


вот пример того, как вы можете сделать быстрый интерпретатор BF:

int main(int argc, char **argv)
{
    if( !(argc > 1) )
        return 1;
    unsigned char *progmem = argv[1];
    unsigned char *cellmem = malloc(sizeof(char)*CELLSPACE);
    if(cellmem == NULL)
        return 1; 
    unsigned char **loopdepth = malloc(sizeof(char*)*MAXLOOPDEPTH);
    if(loopdepth == NULL)
        return 1;

    unsigned char *origcellmem = cellmem;
    unsigned char **origloopdepth = loopdepth;

    for(;;)
    {
        switch(*progmem)
        {
            case '+':
                ++*cellmem;
                break;
            case '-':
                --*cellmem;
                break;
            case '>':
                cellmem++;
                break;
            case '<':
                cellmem--;
                break;
            case '[':
                *loopdepth = progmem-1;
                loopdepth++;
                break;
            case ']':
                loopdepth--;
                if(*cellmem)
                {
                    progmem = *loopdepth;
                }
                break;
            case '.':
                putc(*cellmem, stdout);
                break;
            case ',':
                *cellmem = getc(stdin);
                break;
            case '':
                free(origcellmem);
                free(origloopdepth);
                return 0;
        }
        progmem++;
    }
}

Так, основные моменты моего кода, которые должны сделать его быстрее, чем ваше решение:

Я не делаю никакой проверки каждого цикла, компилятор, вероятно, сгенерирует необработанный безусловный цикл здесь(или так мне говорят мастера C.) И поскольку я использую необработанные данные из строки вместо структур, мне просто нужно поставить "\0 " в конце переключателя! Это означает единственный раз, когда мой переводчик проверяет если ему нужно закончить программу, когда ничто другое не соответствует коммутатору.

Я использую простые указатели для всего, и только манипулируя ими, я не делаю арифметику на целых числах, а затем обращаюсь к указанной памяти с операторами [], я просто манипулирую указателями и их указал на память напрямую.

Я использую простой "стек" для хранения циклов, это ограничивает максимальную глубину циклов, которые вы можете иметь, но 32 достаточно для большинства brainfuck программы, и он регулируем в источнике.

Я заказал случай переключателя для того, как часто появляются вещи, видя, как + и - являются наиболее распространенными символами brainfuck, а затем > и

Я не делаю никакой проверки ошибок, я предполагаю, что ввод пользователя идеален. Хотя это может не дать хороших ошибок, таких как запуск границы и тому подобное, это именно то, что языки делают во время выполнения, программа C может легко генерировать segfault, мы, вероятно,- не должны - проверять их. (Быстрая заметка, моя сгенерировала много segfaults при написании этого: P)

и, наконец, одна возможная оптимизация, о которой я подумал:

выполнить кодировку длины, как при сжатии. Вы можете запустить кодирование длины brainfuck в простом формате, так что: + + + превращается в 3+, а интерпретатор "получает" его и вместо добавления одного три раз, он добавляет три один раз. Прирост производительности здесь может быть удивительным в некоторых местах