【问题标题】:C++ Optimization for string compare and replace字符串比较和替换的 C++ 优化
【发布时间】:2015-05-06 19:43:58
【问题描述】:

我正在尝试优化我编写的这个函数,它比较两个字符串,然后替换第一个字符串中的字符(如果在第二个字符串中找不到)。您是否认为,例如在更改为大写时将字符串转换为字符向量会提高性能?但是,我看不到围绕 2 for 循环的许多方法。任何一般提示将不胜感激!

void optimize(std::string & toBeProcessed, const std::string & toBeIgnored, char ch)
{
    std::string upperProcessed = toBeProcessed;
    std::transform(upperProcessed.begin(), upperProcessed.end(), upperProcessed.begin(), ::toupper);
    std::string upperIgnored = toBeIgnored;
    std::transform(upperIgnored.begin(), upperIgnored.end(), upperIgnored.begin(), ::toupper);
    std::vector<char> vectorAfterProcessed;
    bool found;
    for(int i = 0; i <= upperProcessed.size(); i++)
    {
        found = false;
        for(int j = 0; j <= upperIgnored.size(); j++)
        {
            if(upperProcessed[i] == upperIgnored[j])
            {
                vectorAfterProcessed.push_back(upperProcessed[i]);
                found = true;
            }
        }
        if(found != true)
        {
            vectorAfterProcessed.push_back(ch);
        }
    }
    std::string test(vectorAfterProcessed.begin(), vectorAfterProcessed.end());
}

【问题讨论】:

  • 您可以使用std::string::find 而不是第二个循环。不确定它对性能有多大提升,但它会更具可读性。
  • 这个:i &lt;= upperProcessed.size() 会越界。与j 循环相同。
  • 这是您代码中真正的性能瓶颈吗?还是你试图优化“只是因为”?
  • @TheParamagneticCroissant “只是因为”
  • 一件事——如果您使用的是符合 C++ 11 的编译器,您不妨按值传递字符串,而不是通过引用传递,然后在函数中复制这些字符串。 stackoverflow.com/questions/7592630/…

标签: c++ string optimization vector char


【解决方案1】:

请注意,char 只能保存 256 个值。您可以只扫描一次ignored 字符串,然后填充其中出现的字符的位掩码:

uint32_t bitmask[8] = {0};
for(int j = 0; j < upperIgnored.size(); j++)
{
    uint8_t chr = static_cast<uint8_t>(upperIgnored[j]);
    bitmask[chr >> 5] |= (1 << (chr & 31));
}

之后,而不是内部循环,只需检查位掩码的值:

for(int i = 0; i < upperProcessed.size(); i++)
{
    uint8_t chr = static_cast<uint8_t>(upperProcessed[i]);
    if(bitmask[chr >> 5] & (1 << (chr & 31)))
    {
        vectorAfterProcessed.push_back(upperProcessed[i]);
    }
    else
    {
        vectorAfterProcessed.push_back(ch);
    }
}

另请注意,您的代码还有两个问题:您的循环包含右端,这很可能导致段错误/访问冲突,并且在将 found 设置为 true 后您不是 breaking,这如果字符在ignored 字符串中出现多次,则会导致字符多次附加到processed 字符串中。

【讨论】:

  • 把它当作一个位掩码处理很巧妙!现在我很想将它与对缓存不太友好的 bool used[256] 进行比较。
  • 我会用 std::string 替换向量,然后重新测试以查看字符串的 += 是否比 vector::push_back 更有效。
【解决方案2】:

我正处于与 Ishmael 的解决方案类似的阶段,只是我想只使用 256 字节布尔数组,而不是 Ishamel 更易于缓存的 64 字节位掩码数组。

所以我真的很好奇它们之间的表现如何,并快速制定了一个基准。

基准游戏

#include <string>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <vector>
#include <ctime>
#include <cctype>

using namespace std;    

static string optimize_original(string& toBeProcessed, const string& toBeIgnored, char ch)
{
    string upperProcessed = toBeProcessed;
    transform(upperProcessed.begin(), upperProcessed.end(), upperProcessed.begin(), ::toupper);
    string upperIgnored = toBeIgnored;
    transform(upperIgnored.begin(), upperIgnored.end(), upperIgnored.begin(), ::toupper);
    vector<char> vectorAfterProcessed;
    bool found;
    for(size_t i = 0; i <= upperProcessed.size(); i++)
    {
        found = false;
        for(size_t j = 0; j <= upperIgnored.size(); j++)
        {
            if(upperProcessed[i] == upperIgnored[j])
            {
                vectorAfterProcessed.push_back(upperProcessed[i]);
                found = true;
            }
        }
        if(found != true)
            vectorAfterProcessed.push_back(ch);
    }
    return string(vectorAfterProcessed.begin(), vectorAfterProcessed.end());
}

static string optimize_paul(string toBeProcessed, string toBeIgnored, char ch)
{
    transform(toBeProcessed.begin(), toBeProcessed.end(), toBeProcessed.begin(), ::toupper);
    transform(toBeIgnored.begin(), toBeIgnored.end(), toBeIgnored.begin(), ::toupper);
    string test;
    size_t start = 0;
    while (start < toBeProcessed.size())
    {
        size_t n = toBeProcessed.find_first_not_of(toBeIgnored, start);
        if ( n != string::npos)
        { 
            toBeProcessed[n] = ch;
            start = n+1;
        }
        else
            break;
    }
    return toBeProcessed;
}

static string optimize_ike(string input, const string& to_keep, char rep)
{
    bool used[256] = {false};
    for (size_t j=0; j < to_keep.size(); ++j)
    {
        used[tolower(to_keep[j])] = true;
        used[toupper(to_keep[j])] = true;
    }
    for (size_t j=0; j < input.size(); ++j)
    {
        if (used[input[j]])
            input[j] = toupper(input[j]);
        else
            input[j] = rep;
    }
    return input;
}

static string optimize_ishmael(string input, const string& to_keep, char rep)
{
    uint32_t bitmask[8] = {0};
    for (size_t j=0; j < to_keep.size(); ++j)
    {
        const uint8_t lower = static_cast<uint8_t>(tolower(to_keep[j]));
        bitmask[lower >> 5] |= (1 << (lower & 31));

        const uint8_t upper = static_cast<uint8_t>(toupper(to_keep[j]));
        bitmask[upper >> 5] |= (1 << (upper & 31));
    }
    for (size_t j=0; j < input.size(); ++j)
    {
        const uint8_t chr = static_cast<uint8_t>(input[j]);
        if (bitmask[chr >> 5] & (1 << (chr & 31)))
            input[j] = toupper(input[j]);
        else
            input[j] = rep;
    }
    return input;
}

static double sys_time()
{
    return static_cast<double>(clock()) / CLOCKS_PER_SEC;
}

enum {string_len = 10000000};
enum {num_tests = 5};

int main()
{
    const string to_keep = "abcd";
    for (int k=0; k < 5; ++k)
    {
        string in;
        for (int j=0; j < string_len; ++j)
            in += rand() % 26 + 'A';

        double time = sys_time();
        volatile const string a = optimize_original(in, to_keep, '*');
        cout << ((sys_time() - time) * 1000) << " ms for original" << endl;

        time = sys_time();
        volatile const string b = optimize_paul(in, to_keep, '*');
        cout << ((sys_time() - time) * 1000) << " ms for Paul's" << endl;

        time = sys_time();
        volatile const string c = optimize_ike(in, to_keep, '*');
        cout << ((sys_time() - time) * 1000) << " ms for Ike's" << endl;

        time = sys_time();
        volatile const string d = optimize_ishmael(in, to_keep, '*');
        cout << ((sys_time() - time) * 1000) << " ms for Ishmael's" << endl;

        cout << endl;
    }
}

结果

515 ms for original
218 ms for Paul's
78 ms for Ike's
63 ms for Ishmael's

514 ms for original
203 ms for Paul's
78 ms for Ike's
73 ms for Ishmael's

515 ms for original
218 ms for Paul's
78 ms for Ike's
63 ms for Ishmael's

515 ms for original
202 ms for Paul's
67 ms for Ike's
62 ms for Ishmael's

515 ms for original
218 ms for Paul's
78 ms for Ike's
62 ms for Ishmael's

获胜者 -- 伊沙梅尔

在速度方面,赢家似乎是 Ishmael,不仅达到了理论上最快的 O(N+M) 解决方案 [原来是 O(N*M)],而且还获得了最微效率的解决方案。

我相信他的解决方案明显优于我的解决方案。我只是想为后代提供比较所有这些的基准。

从现代 C++ 的角度来看,Paul 的解决方案可能是最优雅的,它利用可用和标准的东西来用更高级别的逻辑替换内部循环。速度并不总是(甚至通常)一切。

【讨论】:

    【解决方案3】:

    注意:无论这是否更快,您都必须分析代码并确定这一点。此外,以下程序针对所有输入案例进行测试。


    如果您的目标是搜索不在字符串中的字符,而不是嵌套搜索循环(和向量),那么在循环中使用 find_first_not_of 将(应该)完成这项工作。

    #include <string>
    #include <algorithm>
    #include <iostream>
    #include <cctype>
    
    std::string optimize(std::string toBeProcessed, std::string toBeIgnored, char ch)
    {
        std::transform(toBeProcessed.begin(), toBeProcessed.end(), toBeProcessed.begin(), ::toupper);
        std::transform(toBeIgnored.begin(), toBeIgnored.end(), toBeIgnored.begin(), ::toupper);
        std::string test;
        size_t start = 0;
        while (start < toBeProcessed.size())
        {
            size_t n = toBeProcessed.find_first_not_of(toBeIgnored, start);
            if ( n != std::string::npos)
            { 
                toBeProcessed[n] = ch;
                start = n+1;
            }
            else
                break;
        }
        return toBeProcessed;
    }
    
    int main()
    {
        std::string out = optimize("abc123", "abc1", 'x');
        std::cout << out;
    }
    

    实时示例:http://ideone.com/RsB37f

    这尚未针对所有输入进行测试,但这说明了基本点。我正在替换字符串,而不是创建一个向量(甚至是一个新字符串)并从头开始构建一个字符串。

    此外,我通过值传递参数,因为使用符合 C++ 11 的编译器执行此操作是有利的。即使您没有 C++ 11 编译器,这样做也不会丢失任何东西,因为在原始示例中,您将传入的字符串复制到局部变量中。

    【讨论】:

      猜你喜欢
      • 2013-08-12
      • 2016-01-31
      • 1970-01-01
      • 2022-12-03
      • 2019-09-30
      • 1970-01-01
      • 1970-01-01
      • 2013-01-26
      • 1970-01-01
      相关资源
      最近更新 更多