我正在开发一个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;
}
}
答案 0 :(得分:2)
正如Bernard所说,两种提议的解决方案的时间和内存复杂性是相同的,第二种解决方案所需的稍微复杂的寻址并不会减慢现代处理器的速度。但我不同意他的建议,即解决方案2可能会更快。&#34;我们真的不知道甚至可以说它理论上应该更快,除了在一些退化的情况下,实际表现的差异可能是不可测量的。
循环的第一次迭代确实可能会更慢。缓存很冷,第一个解决方案需要两个缓存行来存储第一个元素,而第二个解决方案只需要一个缓存行。但在那之后,两种解决方案都在进行前向线性遍历。 CPU在预取额外的缓存行方面没有问题,因此在大多数情况下,初始额外开销实际上不太重要。
另一方面,你在这个循环中写入数据,所以你访问的一些缓存行也被标记为脏(意味着他们的数据最终需要写回共享缓存或主内存,并且它们得到从任何其他核心&#c;缓存中清除。在解决方案1中,取决于sizeof(string)
和sizeof(int)
,只有5-25%的缓存行被标记为脏。但是,解决方案2每一个都会弄脏,因此它实际上可能会占用更多的内存带宽。
因此,可能使解决方案2更快的一些事情是:
...Do Stuff...
非常复杂(足以使保存数据的缓存行从L1缓存中清除)有些事情可能使解决方案1等同于或快于解决方案2:
...Do Stuff...
不是很复杂,因此缓存保持温暖stringsToVisit
读取数据。最重要的是,它可能并不重要。
答案 1 :(得分:0)
首先,您应该对代码进行概要分析,以检查这段代码是否确实是瓶颈,并准确衡量每个解决方案运行所需的时间。这将产生最好的结果。
然而,这是我的答案:
两个解决方案的time complexity都是O(n),所以我们这里只讨论恒定因子优化。
解决方案1需要在每个循环中查找两个不同的内存块 - stringsToVisit [i]和stringVisited [i]。这对于CPU caches来说并不好,与解决方案2相比,循环的每次迭代都会访问存储在内存中连续位置的单个结构。因此,解决方案2的表现会更好。
解决方案2需要比解决方案1更复杂的间接内存查找来访问结构的visited
属性:(base address of stringsToVisit) + (index) * (struct size) + (displacement in struct)
。然而,这种查找非常适合大多数处理器和#39; SIB(scale-index-base)寻址,因此它只会编译成一个汇编指令,所以如果有的话,就不会有太慢的速度。值得注意的是,优化编译器可能会注意到您正在按顺序访问内存并进行优化以避免完全使用SIB寻址。
因此,解决方案2可能会更快。
答案 2 :(得分:-1)
解决方案1比解决方案2更快,因为当编译器解决stringVisited [i]只需要向基本地址(stringVisited)添加位移(i)时,在第二个解决方案中也必须添加成员的位移结构(访问过)。