提高TM模拟器的性能

时间:2017-03-28 16:24:40

标签: c++ performance performance-testing turing-machines

我正在尝试模拟很多2状态,3符号(单向磁带)图​​灵机。每个模拟都有不同的输入,并将运行固定的步数。程序中的当前瓶颈似乎是模拟器,在图灵机器上占用了大量内存而不会停止。

任务是模拟大约650000个TM,每个TM有大约200个非空白输入。我尝试的最大步骤是10亿(10 ** 9)。

以下是我正在运行的代码。 vector<vector<int> > TM是一个转换表。

vector<int> fast_simulate(vector<vector<int> > TM, string TM_input, int steps) {
    /* Return the state reached after supplied steps */

    vector<int> tape = itotape(TM_input);

    int head = 0;
    int current_state = 0;
    int halt_state = 2;

    for(int i = 0; i < steps; i++){

        // Read from tape
        if(head >= tape.size()) {
            tape.push_back(2);
        }
        int cell = tape[head];
        int data = TM[current_state][cell];  // get transition for this state/input

        int move = data % 2;
        int write = (data % 10) % 3;
        current_state = data / 10;

        if(current_state == halt_state) {
            // This highlights the last place that is written to in the tape
            tape[head] = 4;
            vector<int> res = shorten_tape(tape);
            res.push_back(i+1);
            return res;
        }

        // Write to tape
        tape[head] = write;

        // move head
        if(move == 0) {
            if(head != 0) {
                head--;
            }
        } else {
            head++;
        }
    }

    vector<int> res {-1};
    return res;
}

vector<int> itotape(string TM_input) {
    vector<int> tape;
    for(char &c : TM_input) {
        tape.push_back(c - '0');
    }
    return tape;
}

vector<int> shorten_tape(vector<int> tape) {
    /*  Shorten the tape by removing unnecessary 2's (blanks) from the end of it.
    */
    int i = tape.size()-1;
    for(; i >= 0; --i) {
        if(tape[i] != 2) {
            tape.resize(i+1);
            return tape;
        }
    }
    return tape;
}

我是否可以在性能或内存使用方面做出改进?即使减少2%也会产生明显的差异。

3 个答案:

答案 0 :(得分:1)

确保在整个TM模拟过程中不会发生任何分配。

在程序启动时预先分配单个全局数组,这对于磁带的任何状态都足够大(例如 10 ^ 8 元素)。最初将机器放在此磁带阵列的开头。维护段 [0;当前机器模拟访问的所有单元格的R] :这样可以避免在开始新模拟时清除整个磁带阵列。

对于足够的磁带元素使用最小整数类型(例如,如果字母表肯定少于256个字符,则使用unsigned char)。如果字母表非常小,也许您甚至可以切换到位集。这样可以减少内存占用并提高缓存/ RAM性能。

避免在最里面的循环中使用通用整数除法(它们很慢),只使用2次幂的除法(它们变成位移)。作为最后的优化,您可以尝试从最里面的循环中删除所有分支(有各种聪明的技术)。

答案 1 :(得分:1)

以下是更多算法方法的另一个答案。

按块模拟

由于您的字母很小且状态很少,因此您可以通过立即处理磁带块来加速模拟。这与众所周知的speedup theorem有关,虽然我建议采用略有不同的方法。

将磁带分成每个8个字符的块。每个这样的块可以用16位数字表示(每个字符2位)。现在假设机器位于块的第一个或最后一个字符处。然后它的后续行为仅取决于其初始状态和块上的初始值,直到TM移出块(向左或向右)。我们可以预先计算所有(块值+状态+结束)组合的结果,或者可以在模拟期间懒惰地计算它们。

这种方法可以同时模拟大约8个步骤,但如果你运气不好,每次迭代只能做一步(在块边界周围来回移动)。这是代码示例:

//R = table[s][e][V] --- outcome for TM which:
//  starts in state s
//  runs on a tape block with initial contents V
//  starts on the (e = 0: leftmost, e = 1: rightmost) char of the block
//The value R is a bitmask encoding:
//  0..15 bits: the new value of the block
//  16..17 bits: the new state
//  18 bit: TM moved to the (0: left, 1: right) of the block
//  ??encode number of steps taken??
uint32_t table[2][2][1<<16];

//contents of the tape (grouped in 8-character blocks)
uint16_t tape[...];

int pos = 0;    //index of current block
int end = 0;    //TM is currently located at (0: start, 1: end) of the block
int state = 0;  //current state
while (state != 2) {
  //take the outcome of simulation on the current block
  uint32_t res = table[state][end][tape[pos]];
  //decode it into parts
  uint16_t newValue = res & 0xFFFFU;
  int newState = (res >> 16) & 3U;
  int move = (res >> 18);
  //write new contents to the tape
  tape[pos] = newValue;
  //switch to the new state
  state = newState;
  //move to the neighboring block
  pos += (2*move-1);
  end = !move;
  //avoid getting out of tape on the left
  if (pos < 0)
      pos = 0, move = 0;
}

暂停问题

评论说TM模拟预计要么很早就完成,要么将所有步骤都运行到预定义的巨大限制。由于您要模拟许多图灵机,因此可能需要花一些时间来解决halting problem

可以检测到的第一种悬挂方式是:当机器停留在同一个地方而不远离它时。让我们在模拟期间维持TM的周围,这是距离<1的字符段的值。 16来自TM的当前位置。如果您有3个字符,则可以使用62位数字对周围进行编码。

维护TM的每个位置的哈希表(我们稍后会看到,只需要31个表)。在每个步骤之后,将元组(状态,周围)存储在当前位置的哈希表中。现在重要的部分:在每次移动之后,清除距离TM = 16的所有哈希表(实际上,只有一个这样的哈希表必须被清除)。在每个步骤之前,检查哈希表中是否已存在(状态,周围)。如果是,则机器处于无限循环状态。

您还可以检测另一种类型的悬挂:当机器向右无限移动,但永远不会返回。为了实现这一点,您可以使用相同的哈希表。如果TM位于带有索引 p 的磁带的当前最后一个字符处,请检查当前元组(状态,周围)不仅在 p 第六个哈希表中,还要在(p-1) -th,(p-2) -th,...,(p-15) -th hash表。如果找到匹配项,则TM处于向右移动的无限循环中。

答案 2 :(得分:0)

更改

int move = data % 2;

int move = data & 1;

一个是除法,另一个是位掩码,两者都应该在低位上给出0或1个基数。只要你有2的幂,你就可以做到这一点。

您还要设置

cell = tape[head];
data = TM[current_state][cell]; 
int move = data % 2;
int write = (data % 10) % 3;
current_state = data / 10;

每一步,无论磁带[head]是否已更改,甚至在您根本不访问这些值的分支上。仔细查看哪些分支使用哪些数据,并且仅在需要时更新内容。在你写完后直接看:

    if(current_state == halt_state) {
        // This highlights the last place that is written to in the tape
        tape[head] = 4;
        vector<int> res = shorten_tape(tape);
        res.push_back(i+1);
        return res;
    }

^此代码没有引用&#34;移动&#34;或者&#34;写&#34;,所以你可以计算&#34;移动&#34; /&#34;写&#34;在它之后,只计算current_state!= halt_state

if语句的true分支也是优化分支。通过检查暂停状态,并将暂停条件放入else分支,您可以稍微改进CPU分支预测。