作为一个帮助我学习口译和优化的练习,我都不知道,我用C编写了一个脑筋解释器。到目前为止它看起来完美无缺,尽管它在执行速度方面表现不佳到其他快速口译员。
我可以通过哪些方法更改此解释器以提高性能(或其他方式)?
我的解释器的一个有趣的方面(尽管大多数其他人也可能这样做)是我运行一个循环来读取源输入并将每个指令转换为
struct { long instruction; long loop; }
loop
值是匹配的]
指令的索引(如果指令是[
),以及匹配的[
指令的索引(如果指令)是]
,允许快速跳跃。我想这个'解析'过程(不需要很长时间)可以改善执行时间,而不需要在每次需要时进行冗余重新分析以找到匹配的方括号。
一个有趣的测试brainfuck解释器速度是这个程序:
++++++++[->-[->-[->-[-]<]<]<]>++++++++[<++++++++++>-]<[>+>+<<-]>-.>-----.>
instruction
struct instruction
成为操作函数的直接指针 - 这比以前的版本运行速度慢(函数调用开销?) 答案 0 :(得分:18)
我可以看到几种可能性。我认为我的方法是将其转变为生成直接线程代码的编译器。即,当您读取输入时,不是将大多数“指令”或多或少地复制到内存中,而是编写代码以将每个指令实现为函数,并将指向每个函数的指针复制到内存中。然后执行代码将包括按顺序调用这些函数。我可能会让该函数返回下一条要执行的指令的索引(或可能是地址),所以你最终得到的结果如下:
typedef int return_type;
typedef return_type (*f)(void);
f *im = malloc(sizeof(f) * ia);
ci = (*(im[ci]))();
对于每条指令,我还有三个独立的函数,每个指令对应一个BF_END_ *模式,因此您只需要在“编译”阶段处理它。执行代码时,您将直接指向正确的函数。
编辑:
我一直在玩代码。我已经将循环地址分成一个单独的数组,并将大部分解析合并在一起,所以它看起来像这样:
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;
}
答案 1 :(得分:18)
嗯,这不是C.而且它不是一个插件。所以,是的,这个问题几乎完全不合适。
但它是什么,是一个使用C ++ 0x可变参数模板的完全可移植的brainfuck编译器。您必须将#define PROGRAM
作为逗号分隔的C语法字符序列,因为我无法在编译时从字符串中提取它们。但除此之外它是合法的。我想。
使用g++ -std=c++0x -O2 -Wall
测试g ++ 4.5.2。
#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;
}
答案 2 :(得分:7)
答案 3 :(得分:6)
Brainfuck应该很容易编译成C代码,然后编译并执行。那可能是一个非常快速的BF“翻译”。
基本上,您所要做的就是为每个brainfuck运算符生成非常简单的代码 在程序中从左到右。人们可以很容易地优化+和 - 的序列; 类似地,可以优化&lt;和&gt;,通过缓存最近的每个计数 遇到。这是一种窥视孔优化。
这是一个草稿编译器,在命令行上接受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 '\0':
printf("}");
if (nesting>0)
{ printf("Unmatched '['\n");
return 1;
}
return 0;
}
}
}
这是受到马修布兰查德代码启发的关键(感谢马修!), 但未经测试。我会把它留给其他灵魂;随意修补代码 如果你发现问题。如果将代码写入文件,显然会得到改进: - }
[我使用了http://en.wikipedia.org/wiki/Brainfuck文章 代码生成的明显灵感]。
OP的BF计划:
++++++++ [ - &GT; - [ - &GT; - [ - &GT; - [ - ]&LT;] LT;] LT;]&GT; ++++++++并[d ; ++++++++++&GT; - ]的百分比抑制率&GT + GT + LT;&LT; - &GT; - &GT; -----&GT;
应该编译为(缩进添加):
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操作的一个机器指令。
真正雄心勃勃的人会计算ptr的可能值 在计划的每个点;我猜在很多情况下它指的是一个恒定的细胞。 然后可以避免间接访问。
如果你真的想要坚持下去,你可以弄清楚BF命令的作用 直到第一个输入请求;那必须是一个“常量”的初始内存配置, 并使用该常量生成CELLSPACE intializer,并生成代码 对于程序的其余部分,我所展示的方式。如果你这样做,OP的例子 程序将消失在单个CELLSPACE初始化器和几个putc调用中。
答案 4 :(得分:3)
使用LLVM JIT基础架构并让它为您优化代码......
编辑:实际上它已经多次完成了;编译器:http://www.remcobloemen.nl/2010/02/brainfuck-using-llvm/ JIT:https://github.com/resistor/BrainFTracing(注意“编译器”的长度是230行,还算空行,注释和#includes)
edit2:对于downvoter:因为你似乎错过了它,我的回答的意思是“不要重新发明轮子”
答案 5 :(得分:2)
我会看到几件事。
由于错误处理,您的switch
非常复杂。尝试重新组织,只在交换机内部有快速路径,并调用一个或多个函数来查找错误。通常,在switch
内获得代码的时间越短,在那里使用的变量就越少,优化器就能越好。
你有太多的间接。例如,您的索引ci
并不适用。有一个指向实际单元格的指针。这样可以保存寄存器。您可以使用ii
执行类似操作。不要保留指令的数量,只需指向cm
中的位置。
在任何情况下,请检查生成的汇编程序。您将很快看到编译器产生过多寄存器溢出或类似事件的位置。
答案 6 :(得分:1)
以下是如何制作快速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 '\0':
free(origcellmem);
free(origloopdepth);
return 0;
}
progmem++;
}
}
好吧,我的代码的亮点应该比它的解决方案更快:
我没有对每个循环进行任何检查,编译器可能会在这里生成一个原始的无条件循环(或者C向导告诉我。)因为我使用的是字符串中的原始数据而不是结构我只需将'\ 0'放在开关的末尾!这意味着我的解释器检查是否需要结束程序的唯一时间是没有其他任何东西与开关匹配。
我正在使用简单的指针来处理所有事情,并且只操纵那些,我不是对整数进行算术运算,然后用[]运算符访问指向内存,我只是操纵指针并指向内存直接
我正在使用一个简单的'堆栈'来保持循环,这限制了你可以拥有的最大循环深度,但32对于大多数脑力计划来说足够了,而且它在源头上可以调整。
我订购了开关盒的情况,看起来像是+和 - 是最常见的脑圈字符,然后&gt;和&lt;然后[和]最后输入字符。我不是百分之百,但我很确定 - 开关的顺序很重要,如果只是一个小小的位置!
我没有进行任何错误检查,我假设用户的输入是完美的。虽然这可能不会产生很好的错误,比如跑出界限等等,这就是运行时语言的完整性,C程序可以轻松生成段错误,我们可能 - 不应该 - 检查这些错误。 (快速说明,我写了很多段错误:P)
最后我想到了一个可能的优化:
运行长度编码,就像在压缩中使用一样。你可以用简单的格式对大脑进行长度编码,这样:+++变成3+,解释器“得到”它而不是加三次,它增加三次。这里的性能提升可能在某些地方令人惊叹
你去了,那就是我所拥有的一切。我不太了解C,所以我可能犯了一些错误,但我尽我所能,随意对提供的代码进行基准测试,我不知道它运行的速度有多快。它接受输入作为命令行参数。