更快地解析代码文件

时间:2015-12-04 17:19:32

标签: c++ string parsing c++11

我为基于堆栈的语言编写了一个相当复杂的解析器,它将文件加载到内存中,然后通过比较令牌来查看它是否被识别为操作数或指令。

每次我必须解析一个新的操作数/指令I std::copy从文件缓冲区到std::string的内存,然后执行一个`

if(parsed_string.compare("add") == 0) { /* handle multiplication */} 
else if(parsed_string.compare("sub") == 0) { /* handle subtraction */ } 
else { /* This is an operand */ }

遗憾的是,所有这些副本都使解析速度变慢。

我应该如何处理这个以避免所有这些副本?我一直以为我不需要一个tokenizer,因为语言本身和逻辑非常简单。

编辑:我在添加代码的地方获取各种操作数和指令的副本

  // This function accounts for 70% of the total time of the program
  std::string Parser::read_as_string(size_t start, size_t end) {

    std::vector<char> file_memory(end - start);
    read_range(start, end - start, file_memory);
    std::string result(file_memory.data(), file_memory.size());
    return std::move(result); // Intended to be consumed
  }

  void Parser::read_range(size_t start, size_t size, std::string& destination) {

    if (destination.size() < size)
      destination.resize(size); // Allocate necessary space

    std::copy(file_in_memory.begin() + start,
      file_in_memory.begin() + start + size,
      destination.begin());
  }

4 个答案:

答案 0 :(得分:6)

此复制不是必需的。你可以操作切片。

struct StrSlice {
  StrSlice(const std::string& embracingStr, std::size_t startIx, std::size_t length)
  : begin_(/* todo */), end_(/* todo */) // Assign begin_ and end_ here 
  {}

  StrSlice(const char* begin, const char* end)
  : begin_(begin), end_(end) 
  {}
  // Define some more constructors
  // Be careful about implicit conversions
  //...

  //Define lots of comparasion routines with other strings here
  bool operator==(const char* str) const {
    ... 
  }

  bool operator==(const StrSlice& str) const {
    ... 
  } 

  // You can take slice of a slice in O(1) time
  StrSlice subslice(std::size_t startIx, std::size_t length) {
    assert(/* do some range checks here */);
    const char* subsliceBegin = begin_ + startIx;
    const char* subsliceEnd = subsliceBegin + length;
    return StrSlice(subsliceBegin, subsliceEnd); 
  }
private:
  const char* begin_;
  const char* end_;
}; 

我希望你明白这个主意。当然,在关联字符串中的任何更改之后,此片段将会中断,尤其是内存重新分配。但是,除非您阅读新文件,否则您的字符串似乎无法更改。

答案 1 :(得分:0)

它可能不仅仅是复制,而且还有级联的字符串比较(假设你有超过你展示的两个指令)。

您可以尝试使用查找表(如std :: map或std :: unordered_map)将指令转换为您打开的枚举类型。所以而不是:

if(parsed_string.compare("add") == 0) { /* handle multiplication */}
else if(parsed_string.compare("sub") == 0) { /* handle subtraction */ }
...
else { /* This is an operand */ }

你做:

const auto it = keywords.find(parsed_string);
if (it != keywords.end()) {
  switch (it->second) {
    case kAdd:  // handle addition
    case kSub:  // handle subtraction
    ...
  }
} else {
  // handle operand
}

如果有多个关键字,这将导致更少的字符串比较,此时副本可能不是那么大的交易。如果是这样,这个建议可以与其他使用&#34;切片&#34;的人一起使用。或者&#34;观点&#34;进入实际数据以避免复制。

答案 2 :(得分:0)

这个怎么样:

std :: string Parser :: read_as_string(size_t start,size_t end)
{
return file_in_memory.substr(start,end);
}

除了开销之外,你的“read_as_string”函数只会执行标准的“substr”...

答案 3 :(得分:0)

将输入流的前缀与关键字的常量字符串进行比较很容易编码,但肯定不是很快;如果您有N个关键字,您将进行O(N)字符串比较。如果字符串的平均长度为L,则您将进行O(N * L)字符比较。这样的比较不会让你拿起数字,标识符或字符串文字,你不能只是比较一个常量字符串。 (并且复制前缀似乎没有帮助)。

你应该考虑做的是构建一个基于有限状态的机器来实现你的词法分析器。这是 解决方案,几乎是地球上每个生产解析器/编译器使用的,因为它们往往非常快。 设计精良的FSA将对输入字符串的每个字符执行单个字符查找;这很难被击败。

您可以手工制作这样的FSA,也可以使用工具。

有关基本背景,请参阅http://en.wikipedia.org/wiki/Lexical_analysis, 和广泛用户lexer-generators的具体列表。