先前循环迭代对当前迭代执行时间的影响

MTM*_*TMD 6 c++ time stl c++11 folly

我正在尝试测量folly哈希图中并发插入的性能。用于这种插入的程序的简化版本显示在此处:

#include <folly/concurrency/ConcurrentHashMap.h>
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

const int kNumMutexLocks = 2003;
std::unique_ptr<std::mutex[]> mutices(new std::mutex[kNumMutexLocks]);
__inline__ void
concurrentInsertion(unsigned int threadId, unsigned int numInsertionsPerThread,
                    unsigned int numInsertions, unsigned int numUniqueKeys,
                    folly::ConcurrentHashMap<int, int> &follyMap) {
  int base = threadId * numInsertionsPerThread;
  for (int i = 0; i < numInsertionsPerThread; i++) {
    int idx = base + i;
    if (idx >= numInsertions)
      break;
    int val = idx;
    int key = val % numUniqueKeys;
    mutices[key % kNumMutexLocks].lock();
    auto found = follyMap.find(key);
    if (found != follyMap.end()) {
      int oldVal = found->second;
      if (oldVal < val) {
        follyMap.assign(key, val);
      }
    } else {
      follyMap.insert(key, val);
    }
    mutices[key % kNumMutexLocks].unlock();
  }
}

void func(unsigned int numInsertions, float keyValRatio) {
  const unsigned int numThreads = 12; // Simplified just for this post
  unsigned int numUniqueKeys = numInsertions * keyValRatio;
  unsigned int numInsertionsPerThread = ceil(numInsertions * 1.0 / numThreads);
  std::vector<std::thread> insertionThreads;
  insertionThreads.reserve(numThreads);
  folly::ConcurrentHashMap<int, int> follyMap;

  auto start = std::chrono::steady_clock::now();
  for (int i = 0; i < numThreads; i++) {
    insertionThreads.emplace_back(std::thread([&, i] {
      concurrentInsertion(i, numInsertionsPerThread, numInsertions,
                          numUniqueKeys, follyMap);
    }));
  }
  for (int i = 0; i < numThreads; i++) {
    insertionThreads[i].join();
  }
  auto end = std::chrono::steady_clock::now();

  auto diff = end - start;
  float insertionTimeMs =
      std::chrono::duration<double, std::milli>(diff).count();
  std::cout << "i: " << numInsertions << "\tj: " << keyValRatio
            << "\ttime: " << insertionTimeMs << std::endl;
}

int main() {
  std::vector<float> js = {0.5, 0.25};
  for (auto j : js) {
    std::cout << "-------------" << std::endl;
    for (int i = 2048; i < 4194304 * 8; i *= 2) {
      func(i, j);
    }
  }  
}
Run Code Online (Sandbox Code Playgroud)

问题是在主回路中使用此循环会突然增加func函数中的测量时间。也就是说,如果我直接从main调用函数而没有任何循环(如下所示),则在某些情况下,测量时间会突然缩短100倍以上。

int main() {
  func(2048, 0.25); // ~ 100X faster now that the loop is gone.
}
Run Code Online (Sandbox Code Playgroud)

可能的原因

  1. 在构建hasmap时,我分配了大量内存。我相信当我在循环中运行代码时,在执行循环的第二次迭代时,计算机正忙于为第一次迭代释放内存。因此,程序变得慢得多。在这种情况下,如果有人可以提出一个建议,使我可以通过循环获得相同的结果,我将不胜感激。

更多细节

请注意,如果我在main中展开循环,则会遇到相同的问题。也就是说,以下程序具有相同的问题:

int main() {
  performComputation(input A);
  ...
  performComputation(input Z);
} 
Run Code Online (Sandbox Code Playgroud)

样本输出

第一个程序的输出如下所示:

i: 2048     j: 0.5  time: 1.39932
...
i: 16777216 j: 0.5  time: 3704.33
------------- 
i: 2048     j: 0.25 time: 277.427 <= sudden increase in execution time
i: 4096     j: 0.25 time: 157.236
i: 8192     j: 0.25 time: 50.7963
i: 16384    j: 0.25 time: 133.151
i: 32768    j: 0.25 time: 8.75953
...
i: 2048     j: 0.25 time: 162.663
Run Code Online (Sandbox Code Playgroud)

func 在main中单独运行with i=2048j=0.25产生:

i: 2048     j: 0.25 time: 1.01
Run Code Online (Sandbox Code Playgroud)

任何意见/见解均受到高度赞赏。

Joh*_*qua 2

我对确切的细节不太有信心,但在我看来,这是构建地图时内存分配的结果。unordered_map我使用 plain和 single复制了您所看到的行为mutex,并使地图对象func static完全修复了它。(实际上现在第一次会稍微慢一些,因为还没有为映射分配内存,然后每次后续运行都会更快且时间一致。)

我不确定为什么这会产生影响,因为地图已被破坏并且内存应该已被释放。由于某种原因,映射的释放内存似乎不会在后续调用中重用func。也许其他比我更有知识的人可以详细说明这一点。

编辑:减少最小的、可重现的示例和输出

void func(int num_insertions)
{
    const auto start = std::chrono::steady_clock::now();

    std::unordered_map<int, int> map;
    for (int i = 0; i < num_insertions; ++i)
    {
        map.emplace(i, i);
    }

    const auto end = std::chrono::steady_clock::now();
    const auto diff = end - start;

    const auto time = std::chrono::duration<double, std::milli>(diff).count();
    std::cout << "i: " << num_insertions << "\ttime: " << time << "\n";
}

int main()
{
    func(2048);
    func(16777216);
    func(2048);
}
Run Code Online (Sandbox Code Playgroud)

使用非静态地图:

i: 2048 time: 0.6035
i: 16777216     time: 4629.03
i: 2048 time: 124.44
Run Code Online (Sandbox Code Playgroud)

使用静态地图:

i: 2048 time: 0.6524
i: 16777216     time: 4828.6
i: 2048 time: 0.3802
Run Code Online (Sandbox Code Playgroud)

另一个编辑:还应该提到静态版本还需要在最后调用map.clear(),尽管这与插入的性能问题并不真正相关。