std::string::erase和std::string::substr哪个更快地获取字符串的一部分?

6
我正在检索和存储字符串的一部分,可以使用std::string::erase或std::string::substr。我想知道以下哪种方法更快(完成时间更短)和更有效(内存分配/重新分配更少)。此外,有关erase和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);
}

4
这个比较的意义在哪里?这两个函数的作用不同。如果你想要获取一个子字符串,就使用 substr 函数。如果你想要从一个字符串中删除内容,就使用 erase 函数。 - Luchian Grigore
@LuchianGrigore:我需要获取原始字符串中的“NodeName”部分,有两种选择。我现在正在使用substr,但只是想知道是否对于这个需求使用erase可能更好,尽管substr似乎更直观。 - KK_35
@lc2817:我不确定如何检查内存分配/重新分配。 - KK_35
@KK_35,http://valgrind.org 或者谷歌搜索“C++内存分配分析器” - lc2817
1
@KK_35 请使用 substr,即使仅凭纯粹的奇迹 erase 更快(我真的怀疑)。你所说的“直观性”极大地弥补了任何性能损失(虽然可能没有)。 - Luchian Grigore
显示剩余3条评论
3个回答

5

这里有一个基准测试,其中包括了字符串的erase和substr操作。这个用例有点不同,因为我试图从每个单词中删除一个字母。实际表明,Erase在这种情况下比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;
}

4

如果你真的在意,就要不断地进行基准测试。

你不需要像这样进行自我分配: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());

0

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

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

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

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

编辑:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接