STL排序在字符串矢量与字符串指针矢量上的性能比较
Performance comparison of STL sort on vector of strings vs. vector of string pointers
我试图比较STL排序在字符串向量和指向字符串的指针向量上的性能。
我预计指针版本会更好,但500万个随机生成的字符串的实际结果是
字符串矢量:12.06秒
字符串指针矢量:16.75秒
是什么解释了这种行为?我希望交换指向字符串的指针应该比交换字符串对象更快。
这500万个字符串是通过转换随机整数生成的
使用(gcc 4.9.3)编译:g++ -std=c++11 -Wall
CPU:Xeon X5650
// sort vector of strings
int main(int argc, char *argv[])
{
const int numElements=5000000;
srand(time(NULL));
vector<string> vec(numElements);
for (int i = 0; i < numElements; i++)
vec[i] = std::to_string(rand() % numElements);
unsigned before = clock();
sort(vec.begin(), vec.end());
cout<< "Time to sort: " << clock() - before << endl;
for (int i = 0; i < numElements; i++)
cout << vec[i] << endl;
return 0;
}
// sort vector of pointers to strings
bool comparePtrToString (string *s1, string *s2)
{
return (*s1 < *s2);
}
int main(int argc, char *argv[])
{
const int numElements=5000000;
srand(time(NULL));
vector<string *> vec(numElements);
for (int i = 0; i < numElements; i++)
vec[i] = new string( to_string(rand() % numElements));
unsigned before = clock();
sort(vec.begin(), vec.end(), comparePtrToString);
cout<< "Time to sort: " << clock() - before << endl;
for (int i = 0; i < numElements; i++)
cout << *vec[i] << endl;
return 0;
}
这是因为sort
对strings
执行的所有操作都是移动和交换。std::string
的移动和交换都是恒定时间操作,这意味着它们只涉及更改一些指针。
因此,对于这两种类型,数据的移动具有相同的性能开销。然而,如果是指向字符串的指针,则需要在每次比较时支付一些额外的费用来取消引用指针,这会导致比较速度明显较慢。
在第一种情况下,到字符串表示的内部指针被交换,而不是复制完整的数据。
您不应该期望从使用指针的实现中获得任何好处,事实上,指针的实现速度较慢,因为为了执行比较,指针必须被取消引用。
是什么解释了这种行为?我期望交换指向字符串的指针应该比交换字符串对象更快。
这里发生了各种可能影响性能的事情。
-
交换是相对便宜的两种方式对于大字符串,交换字符串往往是一种浅操作(只是交换指针和积分等POD),对于小字符串,可能是深操作(但仍然相当便宜——取决于实现)。因此,交换字符串总体上往往非常便宜,通常不会比简单地交换指向它们的指针贵多少。
sizeof(string)
肯定比sizeof(string*)
大,但这基本上不是天文数字,因为运算仍然在恒定的时间内发生,在这种情况下要便宜得多当字符串字段已经被提取到一个更快的表单中时比较器的内存,为我们提供时间位置关于其领域。] -
字符串内容必须双向访问即使是比较器的指针版本也必须检查字符串内容(包括指定
size
和capacity
的字段)。因此,不管怎样,我们最终都要为获取字符串内容的数据支付内存成本。当然,如果你只是按指针地址对字符串进行排序(例如:不使用比较器),而不是对字符串内容进行字典式比较,那么性能优势应该向指针版本转移,因为这将大大减少访问的数据量,同时提高空间局部性(例如,可以在缓存行中容纳比字符串更多的指针) -
指针版本正在分散(或至少增加)内存中的字符串字段对于指针版本,您将在免费存储中分配每个字符串(除了可能在免费存储上分配或不分配的字符串内容)。这可以分散内存并减少引用的局部性,因此,通过增加缓存未命中,您可能会在比较器中产生更大的成本。即使这种顺序分配导致分配非常连续的页面集(理想情况),由于分配元数据/对齐开销(并非所有分配器都要求元数据直接存储在块中,但通常它们至少会给块大小增加一些小开销),因此从一个字符串的字段到下一个字符串字段的步长往往会变大一点。
将其归因于取消引用指针的成本可能更简单,但与其说
mov/load
指令执行内存寻址的成本很高(在这种相对上下文中),不如说是从尚未缓存/分页的较慢/较大形式的内存加载到较快、较小的内存。在免费存储中单独分配每个字符串通常会增加这一成本,无论是由于相邻性的丢失还是每个字符串条目之间的较大恒定步长(在理想情况下)。即使在不太努力地诊断存储器级别上发生的事情的基本级别上,这增加了机器必须查看的数据的总大小(字符串内容/字段+指针地址),此外还减少了局部性/较大或可变的步幅(通常,如果你增加了访问的数据量,它至少必须具有改进的局部性,才有很好的机会受益)。如果您只是对指向连续分配的字符串的指针进行排序,您可能会开始看到更多可比较的时间(不是根据我们无法控制的字符串内容,而是根据相邻的字符串对象本身进行排序——实际上是指向存储在数组中的字符串的指示器)。然后,除了将关联得更紧密的数据打包在一个连续的空间中之外,您还可以至少为字符串字段返回空间位置。
交换较小的数据类型(如索引或指针)有时会带来好处,但它们通常需要避免检查它们所引用的数据的原始内容,或者提供明显更便宜的交换/移动行为(在这种情况下,字符串已经很便宜,在考虑时间位置的情况下会变得更便宜),或者两者兼而有之。
嗯,std::string
通常是std::string*
的3-4倍大
所以,只要直接交换前两个,就会打乱更多的记忆。
但这与以下影响相比相形见绌:
- 引用的位置。您需要再跟随一个指针到一个随机位置来读取字符串
- 更多的内存使用:每个
std::string
的每次分配都有一个指针加上记账
两者都对缓存提出了额外的要求,前者甚至无法预取。
交换容器只更改容器的内容,在字符串的情况下,指针指向字符串的第一个字符,而不是整个字符串。
在字符串指针的矢量的情况下,您执行了一个额外的步骤-强制转换指针
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 通过默认复制构造函数比较 C++ 字符串是否会影响性能,原因为何?
- 性能 - 使用字符串构造函数与使用串联
- C++和Java的字符串循环性能比较
- 检查从查询返回的任何行是否包含在字符串中的最高性能方法?
- MongoC ++驱动程序BSON构造:基于流与基于字符串解析.哪一个性能更好?
- STD ::移动性能,同时将大字符串作为rvalue
- C++按字符串调用函数,比较PHP的性能,如何在C++中优化代码
- 性能字符串作为函数参数
- C++字符串构造性能
- 字符串操作性能问题
- 字符串与哈希作为映射键 - 性能
- 通过 iStringStream 标记化的 C++ 字符串的性能开销
- 对于静态错误字符串,哪个的内存/性能效率更高,或者有替代方案
- std::用于处理短字符串的字符串性能
- 在将 char 数组复制到字符串期间节省 CPU 周期(提高性能)
- 性能字符串与字符串
- C++代码性能字符串进行比较
- 性能标准::strstr vs. 标准::字符串::查找
- 字符串到flywweights的字符串转换:更好的性能选项