我有一堆代码,其中类型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*)
的{{1}},basic_string::compare( const char* )
调用std::char_traits<char>( const char* )
调用strlen()
来计算字符串文字的长度。然后对两个字符串进行比较,并将两个字符串的长度传递给该比较。
编译器很难分析所有这些并发出遍历字符串文字两次的代码。使用短文字并不是很多时间,但每次比较都涉及遍历文字两次而不是一次。简单地调用strcmp()
很可能会更快。
我可以做些什么,比如编写一个自定义比较器类,有助于避免在这种情况下两次遍历字符串文字?
答案 0 :(得分:11)
与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
NB。最快的解决方案 - 以更多编辑为代价 - 可能是构建一个(完美的)散列或trie映射字符串文字到枚举常量,然后在查找值上只有switch
。长if
/ else if
链通常闻到不好的IMO。
答案 1 :(得分:9)
嗯,除了C ++ 14 string_literal
之外,您可以轻松编写解决方案代码:
要与单个字符进行比较,请使用字符文字和:
bool operator==(const std::string& s, char c)
{
return s.size() == 1 && s[0] == c;
}
为了与字符串文字进行比较,您可以使用以下内容:
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;
}
声明:
答案 2 :(得分:7)
如果您要比较长链字符串文字,那么可能有一些潜在的处理比较前缀和组共同处理的可能性。特别是在将一组已知的字符串与输入字符串进行比较时,还可以选择使用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(")")) {
...
}
答案 3 :(得分:5)
您可以获得的最快字符串比较是通过实习字符串:构建一个包含所有字符串的大型哈希表。确保每当创建一个字符串对象时,首先从哈希表中查找它,如果没有找到预先存在的对象,则只创建一个新对象。当然,这个功能应该封装在你自己的字符串类中。
完成此操作后,字符串比较等同于比较它们的地址。
这实际上是一种先用LISP语言推广的旧技术。
关键是,为什么这更快,是每个字符串只需要创建一次。如果您小心,您将永远不会从相同的输入字节生成两次相同的字符串,因此字符串创建开销由您处理的输入数据量控制。并且对所有输入数据进行哈希处理并不是什么大问题。
另一方面,当你编写某种解析器或解释器时,比较往往会一遍又一遍地涉及相同的字符串(比如你对文字字符串的比较)。这些比较简化为单一机器指令。答案 4 :(得分:4)
其他2个想法:
A)使用像flex这样的词法分析器工具构建FSA,因此字符串将转换为整数标记值,具体取决于它匹配的内容。
B)使用长度,打破长的其他链,可能是部分表驱动的
为什么不把字符串的长度放在顶部,然后只是与它可能匹配的文字进行比较。
如果有很多这样的话,可能值得使它成为表驱动并使用地图和函数指针。您可以使用单个字符文字,例如,可能使用函数查找表。
快速查找不匹配和常见长度可能就足够了,不需要太多的代码重构,但更易于维护和更快。
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
}
}
答案 5 :(得分:2)
这不是最漂亮的解决方案,但是当有很多短字符串要比较时(例如脚本解析器中的运算符和控制字符/关键字?),它已经证明非常快。
根据字符串长度创建搜索树,仅比较字符。尝试将已知字符串表示为枚举,如果这使得它在特定实现中更清晰。
简短的例子:
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;
}
}