将std :: string转换为大写:主要性能差异?

时间:2012-02-29 22:48:41

标签: c++

所以我正在玩一些代码,并希望看到将std :: string转换为大写的哪种方法效率最高。我认为两者在性能方面会有些相似,但我非常错误。现在我想找出原因。

转换字符串的第一种方法如下:对于字符串中的每个字符(保存长度,从0到长度迭代),如果它在'a'和'z'之间,则将它移到它之间“A”和“Z”代替。

第二种方法的工作方式如下:对于字符串中的每个字符(从0开始,一直持续到我们点击空终止符),在toupper()函数中应用构建。

以下是代码:

#include <iostream>
#include <string>

inline std::string ToUpper_Reg(std::string str)
{
    for (int pos = 0, sz = str.length(); pos < sz; ++pos)
    {
        if (str[pos] >= 'a' && str[pos] <= 'z') { str[pos] += ('A' - 'a'); }
    }

    return str;
}

inline std::string ToUpper_Alt(std::string str)
{
    for (int pos = 0; str[pos] != '\0'; ++pos) { str[pos] = toupper(str[pos]); }

    return str;
}


int main()
{
    std::string test = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^&*()_+=-`'{}[]\\|\";:<>,./?";

    for (size_t i = 0; i < 100000000; ++i) { ToUpper_Reg(test); /* ToUpper_Alt(test); */ }

    return 0;
}

第一种方法ToUpper_Reg每1亿次迭代花费 169秒 第二种方法Toupper_Alt每1亿次迭代花费 379秒

是什么给出了?


编辑:我更改了第二个方法,以便它迭代字符串第一个方法(将长度放在一边,循环时小于长度)并且它有点快,但仍然大约两倍很慢。


编辑2:感谢大家的提交!我将使用它的数据保证是ascii,所以我认为我将暂时坚持使用第一种方法。我会记住,toupper是特定于何时/如果我需要它的区域设置。

8 个答案:

答案 0 :(得分:13)

std::toupper使用当前语言环境进行大小写转换,包括函数调用和其他抽象。很自然地,它会变慢。但它也适用于非ASCII文本。

答案 1 :(得分:5)

toupper()不仅可以移动[a-z]范围内的字符。一方面,它依赖于语言环境,可以处理的不仅仅是ASCII。

答案 2 :(得分:3)

toupper()将区域设置考虑在内,因此它可以处理(某些)国际字符,并且比处理字符范围'a' - 'z'要复杂得多。

答案 3 :(得分:3)

好吧,ToUpper_Reg()不起作用。例如,它不会将我的名字变成全部大写字符。也就是说,ToUpper_Alt()也不起作用,因为toupper()在某些平台上传递了负值,即在使用我的名字时会产生未定义的行为(通常是崩溃)。但是,通过正确地调用它可以很容易地解决这个问题:

toupper(static_cast<unsigned char>(str[pos]))

也就是说,代码的两个版本不相同:使用toupper()的版本onot不是一直写字符而后一个版本是:一旦所有内容都转换为大写它总是在测试后采用相同的分支,然后什么都不做。您可能希望将ToUpper_Alt()更改为如此并重新测试:

inline std::string ToUpper_Alt(std::string str)
{
    for (int pos = 0; str[pos] != '\0'; ++pos) { 
        if (islower(static_cast<unsigned char>(str[pos])) {
           str[pos] = toupper(static_cast<unsigned char>(str[pos]));
        }
    }

    return str;
}

我猜不同的是写作:toupper()用于交换数组查找的比较。快速访问语言环境,所有toupper()都会获取当前指针并访问给定偏移量的位置。对于缓存中的数据,这可能与分支一样快。

答案 4 :(得分:0)

第二个涉及函数调用。函数调用是内循环中的昂贵操作。 toupper还使用区域设置来确定角色应该如何更改。

呼叫的进步是它是标准的,无论主机上的字符编码如何都能正常工作

那就是说,我强烈建议使用boost功能:

 boost::algorithm::to_upper

它是一个模板,所以很可能内联,但它确实涉及区域设置。我还是会用它。

http://www.boost.org/doc/libs/1_40_0/doc/html/boost/algorithm/to_upper.html

答案 5 :(得分:0)

我想这是因为第二个调用C标准库函数,一方面没有内联,所以你得到了函数调用的开销。但更重要的是,这个功能可能不仅仅是两个比较,两个跳转和两个整数加法。它会对角色执行额外的检查,并将当前的区域设置考虑在内,以及所有这些内容。

答案 6 :(得分:0)

std :: toupper使用当前的语言环境,并且这比C函数慢的原因是当前语言环境是从不同的线程共享和可变的,因此在访问它时需要锁定语言环境对象以确保它不是在通话期间切换。每次调用toupper都会发生这种情况,并且会产生相当大的开销(获取锁可能需要系统调用,具体取决于实现)。如果要获得性能并尊重区域设置,一种解决方法是首先获取区域设置对象(创建本地副本),然后在副本上调用toupper facet,从而避免锁定每个toupper调用。请参阅以下链接以获取示例。

http://www.cplusplus.com/reference/std/locale/ctype/toupper/

答案 7 :(得分:0)

问题已经得到解答,但是除此之外,在第一种方法中用以下方法替换你的循环内容:

std::string::value_type &c = str[pos];
if ('a' <= c && c <= 'z') { c += ('A' - 'a'); }

让它更快。也许我的编译器很糟糕。