【问题标题】:Whis is faster for getting a part of the string, std::string::erase or std::string::substr获取字符串的一部分,std::string::erase 或 std::string::substr 哪个更快
【发布时间】:2013-05-29 06:08:18
【问题描述】:

我正在检索和存储可以使用 std::string::erase 或 std::string::substr 的字符串的一部分。

我想知道以下哪种方法更快(完成时间更短)和高效(更少的内存分配/重新分配)。此外,有关擦除和 substr 如何分配/重新分配内存的任何信息都将非常有帮助。谢谢!

std::string nodeName("ABCD#XYZ#NodeName");
const std::string levelSeparator("#");

选项 1:使用 std::string::substr

std::string::size_type nodeNameStartPosition = nodeName.rfind(levelSeparator);
if (nodeNameStartPosition != std::string::npos)
{
    nodeNameStartPosition += levelSeparator.length();
    nodeName = nodeName.substr(nodeNameStartPosition);
}

选项 2:使用 std::string::erase

std::string::size_type nodeNameStartPosition = nodeName.rfind(levelSeparator);
if (nodeNameStartPosition != std::string::npos)
{
    nodeNameStartPosition += levelSeparator.length();
    nodeName = nodeName.erase(0, nodeNameStartPosition);
}

【问题讨论】:

  • 这个比较有什么意义?两者做不同的事情。如果要获取子字符串,请执行substr。如果要从字符串中删除,请使用erase
  • @LuchianGrigore:我需要获取我有这两个选项的原始字符串的“N​​odeName”部分。我现在正在使用 substr,但只是想知道即使 substr 看起来很直观,使用擦除来满足这个要求是否会更好。
  • @lc2817:我不确定如何检查内存分配/重新分配。
  • @KK_35、valgrind.org 或谷歌“内存分配分析器 c++”
  • @KK_35 请使用substr,即使奇迹般地erase 更快(我真的怀疑)。您所说的“直观”极大地弥补了任何性能损失(尽管可能没有)。

标签: c++ stdstring


【解决方案1】:

如果您真的关心,请始终进行基准测试。

您不需要进行自我分配ala nodeName = nodeName.erase(0, nodeNameStartPosition); - 只需使用:

nodeName.erase(0, nodeNameStartPosition);

这是有效的,因为erase 已经修改了字符串nodeName

任何速度差异都极有可能对erase 有利,因为绝对没有进行内存分配 - 只是在缓冲区内进行复制。 substr() 可能会创建一个临时字符串 - 您可以从 std::string::substr 函数原型中的按值返回类型中看出这一点:

string substr (size_t pos = 0, size_t len = npos) const;

除非启动短字符串优化,否则这种按值返回可能需要堆分配。我怀疑优化器是否可以消除这些开销。

另外,nodeNameStartSeparator 显然是用词不当,因为您将它指向级别分隔符的开头。这一切都归结为:

std::string::size_type levelSeparatorPos = nodeName.rfind(levelSeparator);
if (levelSeparatorPos != std::string::npos)
    nodeName.erase(0, levelSeparatorPos + levelSeparator.length());

【讨论】:

    【解决方案2】:

    这是一个基准,其中包括字符串的擦除和 substr 操作,用例有点不同,因为我试图为单词中的每个字母从单词中删除 1 个字母。在这种情况下,擦除比 substr 更快。

    #include <chrono>
    #include <iostream>
    #include <sstream>
    
    using namespace std;
    
    static const string STR(1000, 'a');
    
    /*
     * remove 1 letter from STR to create a shorter string
     * every letter will be a candidate to remove
     * eg: bcda -> cda, bda, bca, bcd
     *
     * result:
     *
     * stream way takes 63394.1 us
     * append way takes 21007.5 us
     * erase way takes 199.563 us
     * substr way takes 416.735 us
     */
    
    void stream_way() {
        for (int skip = 0; skip < STR.size(); ++skip) {
            stringstream ss;
            for (int i = 0; i < STR.size(); ++i) {
                if (i != skip) {
                    ss << STR[i];
                }
            }
    
            (void) ss.str();
        }
    }
    
    void append_way() {
        for (int skip = 0; skip < STR.size(); ++skip) {
            string s;
            for (int i = 0; i < STR.size(); ++i) {
                if (i != skip) {
                    s += STR[i];
                }
            }
    
            (void) s;
        }
    }
    
    void erase_way() {
        for (int i = 0; i < STR.size(); ++i) {
            string copy = STR;
            copy.erase(i, 1);
            (void) copy;
        }
    }
    
    void substr_way() {
        for (int first_part = 0; first_part < STR.size(); ++first_part) {
            string s = STR.substr(0, first_part) + STR.substr(first_part + 1, STR.size() - first_part - 1);
            (void) s;
        }
    }
    
    int main() {
        auto start = chrono::steady_clock::now();
        stream_way();
        auto end = chrono::steady_clock::now();
        chrono::duration<double, micro> diff = end - start;
        cout << "stream way takes " << diff.count() << " us\n";
    
        start = chrono::steady_clock::now();
        append_way();
        end = chrono::steady_clock::now();
        diff = end - start;
        cout << "append way takes " << diff.count() << " us\n";
    
        start = chrono::steady_clock::now();
        erase_way();
        end = chrono::steady_clock::now();
        diff = end - start;
        cout << "erase way takes " << diff.count() << " us\n";
    
        start = chrono::steady_clock::now();
        substr_way();
        end = chrono::steady_clock::now();
        diff = end - start;
        cout << "substr way takes " << diff.count() << " us\n";
    
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      没有给出完整的上下文,substr 版本应该更快。

      原因是:假设您没有修改原始nodeName(并且您没有显示nodeName 的声明或初始化)。以上暗示您正在复制一个字符串,对其进行初始化,然后对其进行操作。但是,您可以直接使用 substr 变体对其进行初始化,而不是进行复制/擦除。

      即使它不是本地副本,它仍然会更快,因为substr 对内存的写入更少。它将返回一个从开始位置到结束位置的新字符串,只复制所需部分的单个副本(可能是 memcpy)。对于擦除示例,您必须复制整个字符串,然后将所需的所有字符复制到适当的点。

      substr 在几乎所有情况下都应该更快。

      编辑:

      如果您在本地字符串上进行操作,则需要分析以查看差异,并且可能因实施而异。 如果您在本地字符串上进行操作,我希望 erase 表现更好,因为它不需要进行任何分配,但 substr 可能总是会。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-01-26
        • 1970-01-01
        • 2012-08-18
        • 2020-09-02
        • 2014-09-03
        • 2011-01-14
        • 1970-01-01
        相关资源
        最近更新 更多