我正在阅读Nicolai M.Josuttis撰写的“ The C ++ STL。A Tutorial and References”一书,在致力于STL算法的一章中,作者指出: 如果您致电remove() 列表中的元素,该算法不知道它正在对列表进行操作,因此可以执行该操作 适用于任何容器:通过更改元素的值来重新排序元素。例如,如果算法 删除第一个元素,所有随后的元素都分配给它们的先前元素。这个 行为与列表的主要优点相抵触:插入,移动和删除元素的能力 修改链接而不是值。为避免性能下降,列表为所有操纵算法提供了特殊的成员函数。您应该始终喜欢它们。此外,这些成员函数确实删除了“已删除”的元素,如以下示例所示:
#include <list>
#include <algorithm>
using namespace std;
int main()
{
list<int> coll;
// insert elements from 6 to 1 and 1 to 6
for (int i=1; i<=6; ++i) {
coll.push_front(i);
coll.push_back(i);
}
// remove all elements with value 3 (poor performance)
coll.erase (remove(coll.begin(),coll.end(),
3),
coll.end());
// remove all elements with value 4 (good performance)
coll.remove (4);
}
当然,这似乎足以令人信服,但无论如何,我还是决定在PC上运行结果类似的代码,尤其是在MSVC 2013 Environment中。 这是我的即兴代码:
int main()
{
srand(time(nullptr));
list<int>my_list1;
list<int>my_list2;
int x = 2000 * 2000;
for (auto i = 0; i < x; ++i)
{
auto random = rand() % 10;
my_list1.push_back(random);
my_list1.push_front(random);
}
list<int>my_list2(my_list1);
auto started1 = std::chrono::high_resolution_clock::now();
my_list1.remove(5);
auto done1 = std::chrono::high_resolution_clock::now();
cout << "Execution time while using member function remove: " << chrono::duration_cast<chrono::milliseconds>(done1 - started1).count();
cout << endl << endl;
auto started2 = std::chrono::high_resolution_clock::now();
my_list2.erase(remove(my_list2.begin(), my_list2.end(),5), my_list2.end());
auto done2 = std::chrono::high_resolution_clock::now();
cout << "Execution time while using generic algorithm remove: " << chrono::duration_cast<chrono::milliseconds>(done2 - started2).count();
cout << endl << endl;
}
看到以下输出时,我感到很惊讶:
Execution time while using member function remove: 10773
Execution time while using generic algorithm remove: 7459
能否请您解释这种矛盾行为的原因是什么?
答案 0 :(得分:1)
这是一个缓存问题。大多数性能问题都是缓存问题。我们总是想认为该算法是首先要研究的。但是,如果您有意强迫编译器在一次运行中使用不同位置的内存,而在下次运行中全部使用下一位置的内存,则会出现缓存问题。
在构建原始列表时,通过注释new.data <- 1 * outer(X = mat.data[["cat"]], Y = count_cols, `==`)
或new_list = [list(x) for x in FINAL_ZIPPED_LIST]
,我迫使编译器创建代码以在push_back
中创建具有连续内存元素的列表。
push_front
始终位于连续内存中,因为它是在单个副本中分配的。
运行输出:
my_list1
这是我的代码,其中有一个推送注释被注释掉了。
my_list2
通过增加元素数量并反转调用顺序以使擦除首先发生,然后进行删除,则删除会花费更长的时间。同样,这更多是关于缓存,而不是算法或正在完成的工作量。如果您运行的另一个程序会弄脏缓存,检查Internet或移动鼠标,则32 KB L1缓存将被弄脏,并且该运行的性能会下降。