[英]Efficiency of 2 vectors versus a vector of structs
我正在处理一个 C++ 项目,我需要在其中搜索忽略那些已经访问过的向量。 如果已访问过,我将其相应的访问过设置为 1 并忽略它。 哪个解决方案更快?
解决方案1:
vector<string> stringsToVisit;
vector<int> stringVisited;
for (int i = 0; i < stringToVisit.size(); ++i) {
if (stringVisited[i] == 0) {
string current = stringsToVisit[i];
...Do Stuff...
stringVisited[i] = 1;
}
}
或者
解决方案2:
struct StringInfo {
string myString;
int visited = 0;
}
vector<StringInfo> stringsToVisit;
for (int i = 0; i < stringsToVisit.size(); ++i) {
if (stringsToVisit[i].visited == 0) {
string current = stringsToVisit[i].myString;
...Do Stuff...
stringsToVisit[i].visited = 1;
}
}
正如 Bernard 所指出的那样,两个提议的解决方案的时间和内存复杂性是相同的,第二个解决方案所需的稍微复杂的寻址不会在现代处理器上减慢速度。 但我不同意他关于“解决方案 2 可能更快”的建议。 我们真的不知道甚至说它理论上应该更快,而且除了可能在一些退化的情况下,实际性能的差异很可能是无法衡量的。
循环的第一次迭代确实可能会更慢。 缓存是冷的,第一个解决方案需要两个缓存线来存储第一个元素,而第二个解决方案只需要一个。 但是在那之后,两个解决方案都在进行前向线性遍历。 CPU 预取额外的缓存行没有问题,因此在大多数情况下,初始额外开销实际上不太重要。
另一方面,您在此循环中写入数据,因此您访问的某些缓存行也被标记为脏(意味着它们的数据最终需要写回共享缓存或主内存,并且它们会从任何其他内核的缓存)。 在解决方案 1 中,根据sizeof(string)
和sizeof(int)
,只有 5-25% 的缓存行被标记为脏。 然而,解决方案 2 会弄脏每一个,因此它实际上可能会使用更多的内存带宽。
因此,一些可能使解决方案 2 更快的事情是:
...Do Stuff...
非常复杂(足以让保存数据的缓存行从 L1 缓存中清除)一些可能使解决方案 1 等效于或比解决方案 2 更快的事情:
...Do Stuff...
不是很复杂,所以缓存保持温暖stringsToVisit
中读取数据。底线是,这可能无关紧要。
首先,您应该分析您的代码以检查这段代码是否真的是瓶颈,并准确测量每个解决方案运行所需的时间。 那将给出最好的结果。
尽管如此,这是我的答案:
两种解决方案的时间复杂度都是 O(n),所以我们这里只讨论常数因子优化。
解决方案 1 需要在每个循环中查找两个不同的内存块 - stringsToVisit[i] 和 stringVisited[i]。 这对CPU 缓存不利,与解决方案 2 相比,循环的每次迭代访问存储在内存中连续位置的单个结构。 因此,解决方案 2 的性能会更好。
解决方案 2 需要比解决方案 1 更复杂的间接内存查找来访问结构的visited
属性: (base address of stringsToVisit) + (index) * (struct size) + (displacement in struct)
。 尽管如此,这种查找非常适合大多数处理器的 SIB(标度索引基)寻址,因此它只会编译为一条汇编指令,因此不会有太多的缓慢(如果有的话)。 值得注意的是,优化编译器可能会注意到您正在按顺序访问内存并进行优化以避免完全使用 SIB 寻址。
因此,解决方案 2 可能会更快。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.