我在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
中保持温度?
答案 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
课程速度慢得多的唯一原因。我想指出你的实施在哪里浪费时间:
您正在泄漏您的分配。虽然乍一看这看起来有点像作弊,但遗失的free()
实际上却耗尽了你的时间:由于你没有释放你的记忆,下一个malloc()
无法重复使用它。因此,它必须为所有一千万个分配使用新鲜内存,因此它必须从内核请求超过120兆字节。仅这一点就是相当大的开销您永远不会重复使用内存这一事实也意味着您的代码缓存不如可能。
因此,只需添加带
的析构函数~mystring() {
free(_cont);
}
到你的班级节省大约20%的时间在我的机器上。
使用strdup()
:
mystring(const char* str) {
_cont = strdup();
}
再次,这借鉴了标准库实现者的智慧,通过将字符串长度确定与分配和复制融合来获得另外10%。
为了完整起见,我对你的基准测试进行了一些改编:
#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
。但这也是因为我的实现不会检查是否可以应用小字符串优化。