Char * vs C ++中的字符串速度

时间:2010-10-21 15:27:21

标签: c++ string performance char

我有一个C ++程序,它将读取二进制文件中的数据,最初我将数据存储在std::vector<char*> data中。我已经更改了我的代码,以便我现在使用字符串而不是char *,以便std::vector<std::string> data。我必须做的一些更改是从strcmp更改为compare

但是我看到我的执行时间急剧增加。对于示例文件,当我使用char *时需要0.38s,在转换为字符串后,我的Linux机器上花了1.72s。我在Windows机器上观察到类似的问题,执行时间从0.59s增加到1.05s。

我相信这个功能导致速度减慢。它是转换器类的一部分,请注意在变量名末尾用_指定的私有变量。我显然在这里遇到了内存问题,并且陷入了C和C ++代码之间。 我希望这是C ++代码,所以我更新了底部的代码。

我在其他功能中也多次访问ids_names_,因此访问速度非常重要。 通过使用创建map而不是两个单独的向量,我已经能够使用更稳定的C ++代码实现更快的速度。谢谢大家!

示例NewList.Txt

2515    ABC 23.5    32  -99 1875.7  1  
1676    XYZ 12.5    31  -97 530.82  2  
279  FOO 45.5    31  -96  530.8  3  

旧代码:

void converter::updateNewList(){
    FILE* NewList;
    char lineBuffer[100];
    char* id = 0;
    char* name = 0;

    int l = 0;
    int n;

    NewList = fopen("NewList.txt","r");
    if (NewList == NULL){
        std::cerr << "Error in reading NewList.txt\n";
        exit(EXIT_FAILURE);
    } 

    while(!feof(NewList)){
        fgets (lineBuffer , 100 , NewList); // Read line    
        l = 0;
        while (!isspace(lineBuffer[l])){
            l = l + 1;
        }

        id = new char[l];
        switch (l){
            case 1: 
                n = sprintf (id, "%c", lineBuffer[0]);
                break;
            case 2:
                n = sprintf (id, "%c%c", lineBuffer[0], lineBuffer[1]);
                break;
            case 3:
                n = sprintf (id, "%c%c%c", lineBuffer[0], lineBuffer[1], lineBuffer[2]);        
                break;
            case 4:
                n = sprintf (id, "%c%c%c%c", lineBuffer[0], lineBuffer[1], lineBuffer[2],lineBuffer[3]);
                break;
            default:
                n = -1;
                break;
        }
        if (n < 0){
            std::cerr << "Error in processing ids from NewList.txt\n";
            exit(EXIT_FAILURE);
        }

        l = l + 1;
        int s = l;
        while (!isspace(lineBuffer[l])){
            l = l + 1;
        }
        name = new char[l-s];
        switch (l-s){
            case 2:
                n = sprintf (name, "%c%c", lineBuffer[s+0], lineBuffer[s+1]);
                break;
            case 3:
                n = sprintf (name, "%c%c%c", lineBuffer[s+0], lineBuffer[s+1], lineBuffer[s+2]);
                break;
            case 4:
                n = sprintf (name, "%c%c%c%c", lineBuffer[s+0], lineBuffer[s+1], lineBuffer[s+2],lineBuffer[s+3]);
                break;
            default:
                n = -1;
                break;
        }
        if (n < 0){
            std::cerr << "Error in processing short name from NewList.txt\n";
            exit(EXIT_FAILURE);
        }


        ids_.push_back ( std::string(id) );
        names_.push_back(std::string(name));
    }

    bool isFound = false;
    for (unsigned int i = 0; i < siteNames_.size(); i ++) {
        isFound = false;
        for (unsigned int j = 0; j < names_.size(); j ++) {
            if (siteNames_[i].compare(names_[j]) == 0){
                isFound = true;
            }
        }
    }

    fclose(NewList);
    delete [] id;
    delete [] name;
}

C ++ CODE

void converter::updateNewList(){
    std::ifstream NewList ("NewList.txt");

    while(NewList.good()){
        unsigned int id (0);
        std::string name;

        // get the ID and name
        NewList >> id >> name;

        // ignore the rest of the line
        NewList.ignore( std::numeric_limits<std::streamsize>::max(), '\n');

        info_.insert(std::pair<std::string, unsigned int>(name,id));

    }

    NewList.close();
}

更新:跟进问题:Bottleneck from comparing strings并感谢您提供非常有用的帮助!我将来不会犯这些错误!

8 个答案:

答案 0 :(得分:7)

我猜它应该与矢量&lt; string&gt;的性能相关联

关于载体

A std::vector与内部连续数组一起工作,这意味着一旦数组已满,它需要创建另一个更大的数组,并逐个复制字符串,这意味着复制构造和销毁具有相同内容的字符串,这会适得其反......

要轻松确认,请使用std::vector<std::string *>并查看效果是否存在差异。

如果是这种情况,你可以做以下四件事之一:

  1. 如果您知道(或者有一个好主意)向量的最终大小,请使用其方法reserve()在内部数组中保留足够的空间,以避免无用的重新分配。
  2. 使用std::deque,它几​​乎像向量
  3. 使用std::list(不允许您随意访问其项目)
  4. 使用std :: vector&lt; char *&gt;
  5. 关于字符串

    注意:我假设你的strings \ char *被创建一次,而不是修改(通过realloc,append等)。

    如果上述想法不够,那么......

    字符串对象的内部缓冲区的分配类似于char *的malloc,因此您应该看到两者之间很少或没有区别。

    现在,如果你的char *实际上是char[SOME_CONSTANT_SIZE],那么你就避开了malloc(因此,它会比std :: string更快)。

    修改

    阅读更新的代码后,我发现以下问题。

    1. 如果ids_和nam​​es_是向量,如果您对行数有一点了解,那么您应该在ids_和名称_上使用reserve()
    2. 考虑制作ids_和nam​​es_ deque或列表。
    3. faaNames_应该是std :: map,或者甚至是std :: unordered_map(或者你的编译器上的任何hash_map)。你的搜索目前是两个for循环,这是非常昂贵和不合理的。
    4. 在比较字符串的内容之前,请考虑比较字符串的长度。在C ++中,字符串的长度(即std :: string :: length())是零成本操作)
    5. 现在,我不知道你在使用isFound变量做了什么,但是如果你只需找到一个真正的相等,那么我猜你应该研究算法(我不知道是否已经存在一,见http://www.cplusplus.com/reference/algorithm/),但我相信通过思考可以提高搜索效率。
    6. 其他评论:

      1. 忘记在STL中使用int来表示大小和长度。至少,使用size_t。在64位中,size_t将变为64位,而int将保持32位,因此您的代码不是64位就绪(另一方面,我看到传入8个Go字符串的情况很少......但是,更好的是正确的......)
      2. 编辑2

        两个(所谓的C和C ++)代码是不同的。 “C代码”需要长度小于5的ID和名称,否则程序存在错误。 “C ++代码”没有这样的限制。尽管如此,如果确认名称和ID总是小于5个字符,这个限制仍然可以进行大规模优化。

答案 1 :(得分:3)

在开始填充之前,将矢量调整为足够大的大小。或者,使用指向字符串而不是字符串的指针。

事情是每次向量自动调整大小时都会复制字符串。对于诸如指针之类的小对象,这几乎没有任何成本,但对于字符串,整个字符串将被完整复制。

ID和名称应该是string而不是char*,并且可以像这样进行初始化(假设您仍然使用string代替string*):

id = string(lineBuffer, lineBuffer + l);
...
name = string(lineBuffer + s, lineBuffer + s + l);
...
ids_.push_back(id);
names_.push_back(name);

答案 2 :(得分:3)

在修复之前确保它是瓶颈。否则你就是在浪费时间。此外,这种优化是微优化。如果你在C ++中进行微优化,那么考虑使用裸C。

答案 3 :(得分:2)

除了std :: string,这是一个C程序。

尝试使用fstream,并使用探查器检测瓶颈。

答案 4 :(得分:2)

您可以尝试reserve多个vector值,以减少分配数量(这是昂贵的),如同Dialecticus(可能来自古罗马人?)。

但有些东西可能值得一些观察:如何存储文件中的字符串,是否执行连接等...

在C中,字符串(每个人不存在 - 他们没有像STL这样的库中的容器)需要更多的工作来处理,但至少我们知道在处理它们时会发生什么。在STL中,每个方便的操作(意味着需要较少的程序员工作)实际上可能需要在string类中的场景后面进行大量操作,具体取决于您使用它的方式。

因此,虽然分配/释放是一个代价高昂的过程,但其余的逻辑,特别是字符串过程,也可能/应该被视为。

答案 5 :(得分:2)

我认为这里的主要问题是你的字符串版本是两次复制东西 - 首先是动态分配的char[] nameid,然后是std::string s,而你的vector<char *>版本可能不会这样做。要使字符串版本更快,您需要直接读取字符串并删除所有冗余副本

答案 6 :(得分:2)

溪流为你解决了很多繁重的工作。别自己动手,让图书馆帮助你:

void converter::updateNewList(){
    std::ifstream NewList ("NewList.txt");

    while(NewList.good()){
        int id (0);
        std::string name;

        // get the ID and name
        NewList >> id >> name;

        // ignore the rest of the line
        NewList.ignore( numeric_limits<streamsize>::max(), '\n');

        ids_.push_back (id);
        names_.push_back(name);
    }

    NewList.close();
}

无需手动进行空格标记。

此外,您可能会发现此网站是一个有用的参考: http://www.cplusplus.com/reference/iostream/ifstream/

答案 7 :(得分:1)

您可以使用分析器找出代码消耗最多时间的位置。例如,如果您使用gcc,则可以使用-pg编译程序。运行它时,它会将分析结果保存在文件中。您可以在二进制文件上运行gprof以获得人类可读的结果。一旦你知道大部分时间消耗在哪里,你可以发布这段代码以获得更多问题。