【问题标题】:How could I speed up comparison of std::string against string literals?如何加快 std::string 与字符串文字的比较速度?
【发布时间】:2014-07-15 08:12:50
【问题描述】:

我有一堆代码,其中将 std::string 类型的对象与字符串文字进行了比较,以确定其是否相等。像这样的:

//const std:string someString = //blahblahblah;
if( someString == "(" ) {
   //do something
} else if( someString == ")" ) {
   //do something else
} else if// this chain can be very long

比较时间累积到相当多(是的,我分析过),所以加快比较时间会很好。

代码将字符串与许多短字符串文字进行比较,这种比较几乎无法避免。保留声明为std::string 的字符串很可能是不可避免的——这样的代码有数千行。留下字符串文字并与 == 进行比较也可能是不可避免的 - 重写整个代码会很痛苦。

问题在于 Visual C++11 附带的 STL 实现使用了一些奇怪的方法。 == 映射到 std::operator==(const basic_string&, const char*),后者调用 basic_string::compare( const char* ),后者又调用 std::char_traits<char>( const char* ),后者调用 strlen() 来计算字符串文字的长度。然后对两个字符串进行比较,并将两个字符串的长度传递到该比较中。

编译器很难分析所有这些并发出两次遍历字符串文字的代码。使用较短的文字时间不多,但每次比较都涉及遍历文字两次而不是一次。简单地调用strcmp() 很可能会更快。

有什么我可以做的,比如编写一个自定义比较器类来帮助避免在这种情况下两次遍历字符串文字?

【问题讨论】:

  • 如果您分析问题出在对strlen() 的调用上,为什么还要继续与字符串文字进行比较,而不是与一组预先构造的std::string const 对象进行比较?
  • @DietmarKühl:这段代码太多了,我不可能将所有这些文字声明为字符串对象并保持代码可维护。
  • 在不修改客户端代码的情况下拦截比较会很棘手......我会尝试添加template <size_t N> std::operator(const str&, const char (c&)[N]) - 如果这恰好可以更好地明确绑定,那么您可以将其专门用于 N 0,1,2,3,4 并遵循现有的比较,或者例如memcmp,否则。您可以使用例如将 mem 从 c_str() 转换为 int16/int32 进行单一比较....
  • @TonyD:这似乎绑定正确。
  • 您可以通过以下方式避免所有关于 char_traits 和意外计算的内容:char const *s = someString.c_str(); if ( !std::strcmp(s, "(")) { 等。

标签: c++ string performance visual-c++ stl


【解决方案1】:

如果您要比较的字符串文字很长,则可能有可能处理比较前缀与组公共处理。尤其是在将一组已知字符串与输入字符串比较是否相等时,还可以选择使用perfect hash 并将这些操作从它们生成的整数中键入。

由于使用完美哈希可能具有最佳性能,但也需要对代码布局进行重大更改,因此可以在编译时确定字符串文字的大小并在比较时使用此大小。例如:

class Literal {
    char const* d_base;
    std::size_t d_length;
public:
    template <std::size_t Length>
    Literal(char const (&base)[Length]): d_base(base), d_length(Length - 1) {}
    bool operator== (std::string const& other) const {
        return other.size() == this->d_length
            && !other.memcmp(this->d_base, other.c_str(), this->d_length);
    }
    bool operator!=(std::string const& other) const { return !(*this == other); }
};
bool operator== (std::string const& str, Literal const& literal) {
    return literal == str;
}
bool operator!= (std::string const& str, Literal const& literal) {
    return !(str == literal);
}

显然,这假设您的文字不嵌入空字符('\0'),而不是隐式添加的终止空字符,否则静态长度会被扭曲。使用 C++11 constexpr 可以防止这种可能性,但代码在没有任何充分理由的情况下变得有些复杂。然后你会使用类似的东西比较你的字符串

if (someString == Literal("(")) {
    ...
}
else if (someString == Literal(")")) {
    ...
}

【讨论】:

  • 不应该是... memcmp(...) == 0吗?
【解决方案2】:

类似于 Dietmar 的解决方案,但编辑略少:您可以包装字符串(一次)而不是每个文字

#include <string>
#include <cstring>
struct FastLiteralWrapper {
    std::string const &s;

    explicit FastLiteralWrapper(std::string const &s_) : s(s_) {}

    template <std::size_t ArrayLength>
    bool operator== (char const (&other)[ArrayLength]) {
        std::size_t const StringLength = ArrayLength - 1;
        return StringLength == s.size()
            && std::memcmp(s.data(), other, StringLength) == 0;
    }
};

你的代码变成:

const std:string someStdString = "blahblahblah";
// just for the context of the comparison:
FastLiteralWrapper someString(someStdString);
if( someString == "(" ) {
   //do something
} else if( someString == ")" ) {
   //do something else
} else if// this chain can be very long

注意。最快的解决方案——以更多的编辑为代价——可能是构建一个(完美的)哈希或 trie 将字符串文字映射到枚举常量,然后在查找的值上只使用switch。长长的if/else if 链条通常闻起来很糟糕。

【讨论】:

    【解决方案3】:

    嗯,除了 C++14 的 string_literal,您可以轻松编写解决方案:

    1. 为了与单个字符进行比较,请使用字符文字和:

      bool operator==(const std::string& s, char c)
      {
        return s.size() == 1 && s[0] == c;
      }
      
    2. 为了与字符串文字进行比较,您可以使用以下内容:

      template<std::size_t N>
      bool operator==(const std::string& s, char const (&literal)[N])
      {
        return s.size() == N && std::memcmp(s.data(), literal, N-1) == 0;
      }
      

    免责声明:

    • 第一个甚至可能是多余的,
    • 仅当您衡量自己的改进程度时才执行此操作。

    【讨论】:

    • 为了与 char 进行比较(和/或作为 N of 1 的第二个特化),我会尝试 *s.c_str() == c - 我的直觉是它会更快(size() 有在我检查过的每个实现中都需要一个指针减法 - 针对快速 end() 而不是 size() 进行了优化。
    • @Tony 如果s 比您的文字长怎么办?
    • 嗯...我应该只专注于我的电影:-/。这确实需要测试第二个字节的 NUL 也...较小的性能差异。当 rhs 已知为 ASCIIZ 时,您可以根据我之前对该问题的评论使用 cast 和 int16_t 比较,但这在某些系统上存在对齐问题。
    • @Dietmar 确实如此。这很容易解决:-P
    【解决方案4】:

    您可以获得的最快的字符串比较是通过插入字符串: 构建一个包含 所有 已创建字符串的大型哈希表。确保每当创建字符串对象时,首先从哈希表中查找它,如果没有找到预先存在的对象,则仅创建一个新对象。当然,这个功能应该封装在你自己的字符串类中。

    完成此操作后,字符串比较就相当于比较它们的地址。

    这实际上是一种相当古老的技术,最初是在 LISP 语言中普及的。


    之所以这样更快,是因为每个字符串只需要创建一次。如果您小心,您将永远不会从相同的输入字节两次生成相同的字符串,因此字符串创建开销由您处理的输入数据量控制。对所有输入数据进行一次散列处理并不是什么大问题。

    另一方面,当您编写某种解析器或解释器时,比较往往会一遍又一遍地涉及相同的字符串(就像您与文字字符串的比较一样)。并且这些比较被简化为一条机器指令。

    【讨论】:

    • 避免臃肿!你会让硬件人员失业:)
    • 这是一项值得提及的好技术,但根据具体的应用程序要求、数据和硬件,它可能效率低下。诸如“您可以获得的最快字符串比较”和“一次对所有输入数据进行哈希处理并不是什么大问题”之类的语句。在顶部。
    • 从概念上讲,仅散列静态字符串文字类似于 FSA 表驱动的解决方案。只要静态字符串文字的分布均匀,基于 strlen + str[0] + str[1] % NOT_VERY_BIG 数字的快速哈希函数将显着减少比较次数。一个只有 251 个元素的表就足够了,只需要 1KiB
    • 避免很长的 if .. else if 比较链,仍然需要函数 ptrs 完成的处理,但在一个表中。希望 CPU 分支预测器运行良好
    【解决方案5】:

    这不是最漂亮的解决方案,但是当有很多短字符串要比较时(比如脚本解析器中的运算符和控制字符/关键字?),它被证明是相当快的。

    根据字符串长度创建搜索树并且只比较字符。尝试将已知字符串表示为枚举,如果这样可以使其在特定实现中更清晰。

    简短示例:

    enum StrE {
      UNKNOWN = 0 ,
      RIGHT_PAR ,
      LEFT_PAR ,
      NOT_EQUAL ,
      EQUAL
    };
    
    StrE strCmp(std::string str)
    {
      size_t l = str.length();
      switch(l)
      {
        case 1:
        {
          if(str[0] == ')') return RIGHT_PAR;
          if(str[0] == '(') return LEFT_PAR;
          // ...
          break;
        }
        case 2:
        {
          if(str[0] == '!' && str[1] == '=') return NOT_EQUAL;
          if(str[0] == '=' && str[1] == '=') return EQUAL;
          // ...
          break;
        }
        // ...
      }
      return UNKNOWN;
    }
    
    
    
    int main()
    {
      std::string input = "==";
    
      switch(strCmp(input))
      {
        case RIGHT_PAR:
          printf("right par");
          break;
        case LEFT_PAR:
          printf("left par");
          break;
        case NOT_EQUAL:
          printf("not equal");
          break;
        case EQUAL:
          printf("equal");
          break;
        case UNKNOWN:
          printf("unknown");
          break;
      }
    }
    

    【讨论】:

      【解决方案6】:

      其他2个想法:

      A) 使用 flex 之类的词法分析工具构建 FSA,因此字符串将根据匹配的内容转换为整数标记值。

      B) 使用长度,分解长的 elseif 链,可能部分由表驱动

      为什么不获取字符串的长度,在顶部然后与它可能匹配的文字进行比较。

      如果它们很多,则可能值得将其设为表驱动并使用映射和函数指针。您可以只对单个字符文字进行特殊处理,例如可能使用函数查找表。

      快速查找不匹配项和共同长度可能就足够了,并且不需要太多的代码重组,但更易于维护和更快。

      int len = strlen (something);
      if ( ! knownliterallength[ len]) {
          // not match
          ...
      } else {
          // First char may be used to index search, or literals are stored in map with *func()
      
          switch (len)
          {
              case 1:  // Could use a look table index by char and *func()
                  processchar( something[0]);
              break;
      
              case 2: // Short strings
              case 3: 
              case 4:
                  processrunts( something);
              break
      
              default:
              //  First char used to index search, or literals are stored in map with *func()
                  processlong( something);
              break
         }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-12
        • 2017-08-28
        • 2020-04-16
        • 2013-08-22
        • 2013-02-12
        • 2011-12-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多