char *在发布编译中比字符串慢

时间:2017-07-26 10:57:22

标签: c++ arrays string char

我在C ++中创建了自己的字符串类型,我想在insert(equal =)操作中测试std::string vs my string的速度。

class mystring
{
private:

    char * _cont;

public:

    mystring(const char * str) {

        size_t getLen = strlen(str);
        _cont = (char *) malloc(getLen + 1);
        memcpy(_cont, str, getLen);
        _cont[getLen + 1]= '\0';
    }
};

int main() {

    char * str = new char [12];
    strcpy(str,"Hello world");

    auto start_t = chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000000; i++) {
        string storage(str);
    }
    cout << chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now() - start_t).count() << " milliseconds\n";

    start_t = chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000000; i++) {
        mystring storage2(str);
    }
    cout << chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now() - start_t).count() << " milliseconds\n";

    return 0;
}

在这段代码中,我为字符串创建了一个循环来获取char * str,然后为我自己的字符串变量创建了相同的进程 在编译器的 debug 模式下,我得到了这个结果:

1108 milliseconds
531 milliseconds

这里我的字符串比std::string快2倍,以获得值(等于)

但我发现一件重要的事情,当我将编译模式更改为发布时,结果确实不同。 std :: string`快了5倍。

93 milliseconds
472 milliseconds

为什么mystring在发布模式下比std::string慢5倍?我该怎么做才能解决它?

我意识到我是否使用

mystring(const char * str) {
        size_t getLen = strlen(str);
        char temp[getLen + 1];
        memcpy(temp, str, getLen);
        _cont = temp;
    }

mystring在发布模式下速度提高了2倍但在mystring()完成后,char * _cont中没有任何内容保存,因为功能已完成且临时删除自动...有没有办法执行此操作并在完成功能后在_cont中保持温度?

2 个答案:

答案 0 :(得分:2)

  

为什么mystring在Release模式下比字符串慢5倍?

可能是因为您没有实现标准库所做的优化。您的微基准测试特别有利于小字符串优化,其中实现避免在字符串适合字符串对象本身的空间时分配任何动态内存。

  

我该怎么做才能解决它?

实现标准库使用的相同优化。那么,为什么不使用已经优化的字符串实现?

  

我意识到我是否使用

char temp[getLen + 1];
memcpy(temp, str, getLen);
_cont = temp;
     

mystring在Release模式下更快2x但是当mystring()完成时,没有任何东西保存在char * _cont中,因为函数已完成且临时删除了自动

函数返回后访问_cont指向的内存的行为未定义。这个问题比没有保存的更糟糕&#34;。

  

有什么方法可以执行此操作并在完成函数后在_cont中保存temp?

没有。没有办法阻止自动数组在其范围的末尾被销毁。字符串的内存必须在字符串对象中,或者动态分配。

P.S你的字符串在基准测试中通过泄漏分配的内存而不是像标准字符串那样释放它来作弊。

答案 1 :(得分:1)

确实,您的字符串类不包含std::string的实现将包含的优化。最重要的是,您的实现没有小字符串优化,它将在std::string对象中存储小字符串的字符,从而避免在这些情况下动态内存分配。

但是,这并不是你mystring课程速度慢得多的唯一原因。我想指出你的实施在哪里浪费时间:

  1. 您正在泄漏您的分配。虽然乍一看这看起来有点像作弊,但遗失的free()实际上却耗尽了你的时间:由于你没有释放你的记忆,下一个malloc()无法重复使用它。因此,它必须为所有一千万个分配使用新鲜内存,因此它必须从内核请求超过120兆字节。仅这一点就是相当大的开销您永远不会重复使用内存这一事实也意味着您的代码缓存不如可能。

    因此,只需添加带

    的析构函数
    ~mystring() {
        free(_cont);
    }
    

    到你的班级节省大约20%的时间在我的机器上。

  2. 使用strdup()

    可以大大简化您的构造函数
    mystring(const char* str) {
        _cont = strdup();
    }
    

    再次,这借鉴了标准库实现者的智慧,通过将字符串长度确定与分配和复制融合来获得另外10%。

  3. 为了完整起见,我对你的基准测试进行了一些改编:

    #include <chrono>
    #include <iostream>
    #include <stddef.h>
    #include <stdlib.h>
    #include <string.h>
    #include <string>
    #include <sys/wait.h>
    #include <unistd.h>
    
    class StringBase {
        protected:
            char* data;
    };
    
    class String1 : private StringBase {
        public:
            String1(const char* str) {
                size_t getLen = strlen(str);
                data = (char *) malloc(getLen + 1);
                memcpy(data, str, getLen);
                data[getLen + 1]= '\0';
            }
    };
    
    class String2 : private StringBase {
        public:
            String2(const char* str) {
                size_t getLen = strlen(str);
                data = (char *) malloc(getLen + 1);
                memcpy(data, str, getLen);
                data[getLen + 1]= '\0';
            }
            ~String2() { free(data); }
    };
    
    class String3 : private StringBase {
        public:
            String3(const char* str) { data = strdup(str); }
            ~String3() { free(data); }
    };
    
    class String4 : private StringBase {
        public:
            String4(const char* str) {
                size_t getLen = strlen(str);
                data = new char[getLen + 1];
                memcpy(data, str, getLen);
                data[getLen + 1]= '\0';
            }
    };
    
    class String5 : private StringBase {
        public:
            String5(const char* str) {
                size_t getLen = strlen(str);
                data = new char[getLen + 1];
                memcpy(data, str, getLen);
                data[getLen + 1]= '\0';
            }
            ~String5() { delete[] data; }
    };
    
    template<class T>
    void benchmarkString(const char* comment, const char* str) {
        //fork a new process for each benchmark to stop the benchmarks from influencing each other
        if(pid_t child = fork()) {
            waitpid(child, NULL, 0);
        } else {
            std::cout << comment;
            auto startTime = std::chrono::high_resolution_clock::now();
            for (int i = 0; i < 10000000; i++) {
                T storage(str);
            }
            std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count() << " milliseconds\n";
            exit(0);
        }
    }
    
    int main() {
        const char* str = "Hello World!";
        benchmarkString<std::string>("std::string: ", str);
        benchmarkString<String1>("original mystring implementation: ", str);
        benchmarkString<String2>("destructor added: ", str);
        benchmarkString<String3>("destructor added and use of strdup(): ", str);
        benchmarkString<String4>("C++ new used instead of malloc(): ", str);
        benchmarkString<String5>("destructor added: ", str);
    }
    

    使用此基准测试,我在我的机器上获得以下输出:

    std::string: 372 milliseconds
    original mystring implementation: 931 milliseconds
    destructor added: 771 milliseconds
    destructor added and use of strdup(): 709 milliseconds
    C++ new used instead of malloc(): 952 milliseconds
    destructor added: 844 milliseconds
    

    现在,如果我用

    替换源字符串"Hello World!"
    const char* str = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
    

    我明白了:

    std::string: 1132 milliseconds
    original mystring implementation: /* thrashing */
    destructor added: 1036 milliseconds
    destructor added and use of strdup(): 965 milliseconds
    C++ new used instead of malloc(): /* thrashing */
    destructor added: 1100 milliseconds
    

    不同的是,现在字符串对于小字符串优化而言太长了,突然我自己的类优于std::string

    这可能主要是由于new / delete对在我的机器上效率不如malloc() / free()这一事实,因为我敢打赌在std::string上使用new / delete。但这也是因为我的实现不会检查是否可以应用小字符串优化。