即使没有锁也会有内存争用吗?

Can there be memory contention even if there are no locks?

我正在编写一个多线程代码,其中一堆 std::async 调用在整个程序的持续时间内产生固定数量的线程。每个线程都以只读方式处理相同的 const BigData 结构。 const BigData 有频繁的随机读取,但线程在其他方面是完全独立的。人们是否可以合理地期望获得完美的缩放比例,或者是否会因更多的内存访问而出现减速?

编辑:经过一些分析,这似乎是罪魁祸首:

class Point {
  friend Point operator+(const Point& lhs, const Point& rhs) noexcept {
    return Point{lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z};
  };
  friend Point operator-(const Point& lhs, const Point& rhs) noexcept {
    return Point{lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z};
  };

public:
  Point() noexcept;
  Point(const Real& x, const Real& y, const Real& z) noexcept
    : x{x}, y{y}, z{z} {};

private:
  Real x{0};
  Real y{0};
  Real z{0};
};

在重构我的代码以避免对 operator+operator- 的不必要调用之后,我似乎得到了更好的缩放。

是的,速度可能会放缓。主内存 (RAM) 带宽是有限的,如果您有多个内核快速读取大量数据,您可能会使内存总线饱和。最大内存带宽通常为每秒数十 GB(请参阅特定处理器的页面,例如 i9-9900K 显示 41.6 GB/s)。

同样,一个物理包上的所有内核共享一个 L3 缓存,因此如果您多次读取某些数据,您的缓存命中率可能会降低,因为您的线程会将彼此的数据推出 L3(这是最大的缓存)。

如果您想知道某些配置的减速有多少,您只有一个选择:测试它们。如果您提前知道您可能需要什么内存,请考虑在您的代码中添加预取指令,特别是如果您的访问模式是非顺序的。