【问题标题】:Efficiency of 2 vectors versus a vector of structs2 个向量与结构向量的效率
【发布时间】:2017-01-20 03:33:15
【问题描述】:

我正在处理一个 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;
    }
}

【问题讨论】:

  • 您对每个版本都进行了剖析吗?
  • 我想你忘了在解决方案 2 中声明 stringVisited
  • 更清楚地说明您要解决的问题。按照目前的逻辑,第二次你将没有什么可参观的。

标签: c++ performance vector


【解决方案1】:

正如 Bernard 所指出的,两种提议的解决方案的时间和内存复杂性是相同的,并且第二种解决方案所需的稍微复杂的寻址不会减慢现代处理器的速度。但我不同意他关于“解决方案 2 可能更快”的建议。我们实在是不够了解,甚至说理论上应该更快,而且除了可能在少数退化的情况下,实际性能上的差异很可能是无法衡量的。

循环的第一次迭代确实可能会更慢。缓存是冷的,第一种解决方案需要两个缓存行来存储第一个元素,而第二种解决方案只需要一个。但在那之后,两种解决方案都在进行前向线性遍历。 CPU 在预取额外的缓存行时不会有任何问题,因此在大多数情况下,最初的额外开销实际上不太重要。

另一方面,您在此循环中写入数据,因此您访问的某些缓存行也被标记为脏(意味着它们的数据最终需要写回共享缓存或主内存,并且它们得到从任何其他核心的缓存中清除)。在解决方案 1 中,根据 sizeof(string)sizeof(int),只有 5-25% 的缓存行被标记为脏。但是,解决方案 2 会弄脏每一个,因此它实际上可能会使用更多的内存带宽。

因此,可能使解决方案 2 更快的一些事情是:

  • 正在处理的字符串列表非常短
  • ...Do Stuff... 非常复杂(足以将保存数据的缓存行从 L1 缓存中清除)

可能使解决方案 1 与解决方案 2 相同或更快的一些因素:

  • 正在处理的字符串列表从中等到大
  • ...Do Stuff... 不是很复杂,所以缓存保持温暖
  • 程序是多线程的,另一个线程要同时从stringsToVisit读取数据。

最重要的是,这可能无关紧要。

【讨论】:

    【解决方案2】:

    首先,您应该分析您的代码以检查这段代码是否真的是瓶颈,并准确测量每个解决方案运行所需的时间。这样会得到最好的结果。

    不过,这是我的答案:

    两个解决方案的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)。尽管如此,这种查找非常适合大多数处理器的 SIB(基于比例索引)寻址,因此它只会编译为一条汇编指令,因此不会有太多的缓慢,如果有的话。值得注意的是,优化编译器可能会注意到您正在按顺序访问内存并进行优化以避免完全使用 SIB 寻址。

    因此,解决方案 2 可能会更快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-16
      • 2020-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多