为什么std :: map使我的代码变得如此臃肿?

时间:2016-10-27 02:54:22

标签: c++ optimization brainfuck

我最近的娱乐项目是用C ++编写brainfuck解释器。它很简单但今天我决定通过编译步骤添加它。最终的目标是能够创建可执行文件,但现在它所做的只是一些基本的优化。例如,+++++将5个add 1命令转换为单个add 5,依此类推。

虽然效果很好,但我发现剥离后我的可执行文件的大小已经从9k变为12k。经过一些研究后,我确定下面的功能是责任,特别是地图。我不明白为什么。

void Brainfuck::compile(const string& input) {
    map<char, pair<Opcode, int>> instructions {
        { '<', make_pair(Opcode::MOVL,  1) },
        { '>', make_pair(Opcode::MOVR,  1) },
        { '+', make_pair(Opcode::INCR,  1) },
        { '-', make_pair(Opcode::DECR,  1) },
        { '[', make_pair(Opcode::WHILE, 0) },
        { ']', make_pair(Opcode::WEND,  0) },
        { ',', make_pair(Opcode::INP,   0) },
        { '.', make_pair(Opcode::OUTP,  0) },
    };

    string::const_iterator c = begin(input);
    while (c != end(input)) {
        if (instructions.find(*c) != end(instructions)) {
            auto instruction = instructions[*c];
            makeOp(c, instruction.first, instruction.second);
        } else {
            c++;
        }
    }
}

地图中的关键是8个有效的Brainfuck操作之一。该函数遍历输入字符串并查找这些有效字符。根据Brainfuck规范,只会跳过无效字符。如果它找到一个它将该键的map值传递给一个名为makeop的函数进行优化,则将其转换为一个op结构并将其添加到我的解释器实际执行的指令向量中。

op结构有两个成员。操作码,它是基于表示8个操作之一的uint8_t的范围枚举,以及包含操作数的一个int。 (现在是一个操作数。未来更复杂的版本可能有多个操作数的指令。)因此在上面的+++++示例中,结构将包含Opcode :: INCR和5。

因此每个映射条目的值是一个std :: pair,由Opcode和操作数组成。我意识到通用数据结构不可避免地会产生一些开销,但考虑到上述描述的简单性,你不认为3k有点过分吗?

或许这不是有效实现目标的正确方法?但如果不是std :: map那么我应该使用哪种数据结构?

更新

感谢所有回复的人。首先,谈谈我的动机。我经常在日常工作中不使用C ++。因此,我正在做一些娱乐项目,以保持我的知识。具有绝对最小的代码大小并不像例如那么重要。清晰,但我很有兴趣了解这种事情是如何发生的。

根据给出的建议,我的功能现在看起来像这样:

static const int MAXOPS = 8;

void Brainfuck::compile(const string& input) {
    array<tuple<char, Opcode, int>, MAXOPS> instructions {
        make_tuple('<', Opcode::MOVL,  1),
        make_tuple('>', Opcode::MOVR,  1),
        make_tuple('+', Opcode::INCR,  1),
        make_tuple('-', Opcode::DECR,  1),
        make_tuple('[', Opcode::WHILE, 0),
        make_tuple(']', Opcode::WEND,  0),
        make_tuple(',', Opcode::INP,   0),
        make_tuple('.', Opcode::OUTP,  0),
    };

    string::const_iterator c = begin(input);
    while (c != end(input)) {
        auto instruction = find_if(begin(instructions), end(instructions),
        [&instructions, &c](auto i) {
            return *c == get<0>(i);
        });

        if (instruction != end(instructions)) {
            makeOp(c, get<1>(*instruction), get<2>(*instruction));
        } else {
            c++;
        }
    }
}

我重新编译了......没事发生。我记得我有另一种方法,其中包含一个地图和一个(现在已删除?)响应,建议仅仅具有地图实例化就足以添加代码。所以我将该地图更改为数组并重新编译。这次剥离的大小 我的可执行文件从12280到9152。

1 个答案:

答案 0 :(得分:3)

map内部使用平衡树来存储元素。平衡树很快但需要一些代码开销来在插入或删除时重新平衡树。所以3k这个代码合理地接缝。