【问题标题】:Simple bytecode translator简单的字节码翻译器
【发布时间】:2015-06-14 18:27:40
【问题描述】:

我正在尝试使用 C/C++ 构建快速 JIT。它应该将我的字节码翻译成 IA32。是的,我知道 libjit 和类似的东西,但我确信它们不会比这更简单。我以为我找到了一种更快的方法来构建指令,但我错了——传统的 switch/case 方法比我的快 2 倍。我的方法是复制整个块,然后填充模板。我知道现代编译器上的 switch/case 创建了一个跳转表,所以我没有跳转表实现。

我使用了一个 50mb 的文件,其中包含循环到基准测试的 code[] 的内容。 我的配置是:i7,8gb内存,用VS2010编译。

这是我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <time.h>

//unsigned char code[] = {1,2,3,4,4};

void mycopy(unsigned char* to, unsigned char* from, int size) {
    int i = 0;
    while(i < size) {
        /*if (i < size-3) {
            if (*(unsigned int*) &from[i] == 0xFFFFFFFF) {
                *(unsigned int*) &to[i] = 0xC3C3C3C3;
                i += 4;
                continue;
            }
        }*/

        to[i] = from[i];
        i++;
    } 
}

void translateA(unsigned char* code, unsigned char* output, int size) {

    unsigned char A[] = { 3, 1, 1, 1 }; // { size, <bytes...> }
    unsigned char B[] = { 2, 2, 2 };
    unsigned char C[] = { 8, 3, 3, 3, 3, 0xFF, 0xFF, 0xFF, 0xFF };
    unsigned char D[] = { 1, 4 };

    void* templat[] = { &A, &B, &C, &D };

    int i = 0;
    int total = 0;
    while(i < size) {
        int op_index = (int) code[i] - 1;
        unsigned char* instr_buffer = (unsigned char*) templat[op_index];
        int size = (int) instr_buffer[0];
        instr_buffer++;
        mycopy(output+total, instr_buffer,size);
        total += size;
        i++;
    }
}




void translateB(unsigned char* code, unsigned char* output, int size) {
    for(int i = 0; i < size; i++) {
        switch(code[i]) {
            case 1:
                output[0] = 1;
                output[1] = 1;
                output[2] = 1;
                output += 3;
                break;
            case 2:
                output[0] = 2;
                output[1] = 2;
                output += 2;
                break;
            case 3: 
                output[0] = 3;
                output[1] = 3;
                output[2] = 3;
                output[3] = 3;
                output[4] = 0xC3;
                output[5] = 0xC3;
                output[6] = 0xC3;
                output[7] = 0xC3;               
                output += 8;            
                break;
            case 4:
                output[0] = 4;
                output++;
                break;
        }
    }
}

int main(int argc, char* argv[]) {
    // load the 'code' to an array
    FILE* f = fopen("testops.bin", "r+");

    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);

    unsigned char* code = (unsigned char*) malloc(fsize);
    fread(code, fsize, 1, f);
    fclose(f);

    unsigned char* output = (unsigned char*) malloc(fsize*10);
    memset(output, 0x7A, fsize*10);

    // benchmark it
    time_t start = clock();

    // Replace with translateB. It's ~2x faster
    translateA(code, output, fsize);

    printf("\nelapsed %fs\n\n", (float) (clock()-start) / 1000); 

    printf("OUTPUT: ");
    for(int i=0;i<1024;i++) {
        if (output[i] == 0x7A) break;
        printf("%X", output[i]); 
    }

    printf("\n");

    system("PAUSE");
    return 0;
}

编辑:我的问题是,为什么切换代码更快?是不是我做错了什么?

【问题讨论】:

  • 我的问题是为什么切换代码更快?
  • 为什么要创建自己的函数而不是使用memcpy
  • 我将要使用的注释代码的原因。我知道 memcpy 已经过优化,但我正在使用非常小的块。
  • 那么如果你只有很少的操作,可以考虑内联你的函数或者使用宏,这样可以节省一个函数调用。

标签: c assembly x86 jit vm-implementation


【解决方案1】:

编译然后反汇编你的程序,你会得到你的回应。

由于编译器优化,switch case 效率更高,因为它创建了一个跳转表,正如您已经说过的那样。

您的方法进行了更多的地址查找,并且每个字节码有更多的函数调用和循环。

此外,您为什么要使用while 表示您知道哪个迭代次数的循环? 编译器可能对for 循环有额外的优化。

如果有 4 个 case 切换,我不确定你的编译器会创建跳转表,所以实际示例可能会更快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-05
    • 1970-01-01
    • 1970-01-01
    • 2015-08-03
    • 1970-01-01
    • 1970-01-01
    • 2012-02-25
    • 2019-08-15
    相关资源
    最近更新 更多