6

我正在检索和存储可以使用 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);
}
4

3 回答 3

4

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

你不需要做一个自我分配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());
于 2013-05-29T06:28:10.237 回答
4

这是一个基准,其中包括字符串的擦除和 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;
}
于 2016-11-08T22:13:01.233 回答
0

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

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

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

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

编辑:

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

于 2013-05-29T06:31:50.760 回答