在c++;代码 我试图理解一个监视机制的性能效果,我希望它在一个大型C++程序中使用。

在c++;代码 我试图理解一个监视机制的性能效果,我希望它在一个大型C++程序中使用。,c++,multithreading,C++,Multithreading,我有一个容器,里面有来自整个程序的指向double的指针,我有一个单独的线程“监视”这些double,并在它们更新时打印它们的最新值 如果其中一个双打快速改变两次(例如2->3->4),我不在乎错过3。如果其中一个更改了,然后又更改回来(例如,2->3->2),我也不在乎是否遗漏了整个更新。我唯一关心的是最终的值最终会被打印出来。这里主要关注的是,这对主程序的性能有绝对最小的影响,主程序总是以某种任意的顺序读取和写入这些双精度 下面我有一个我认为有效的示例程序,但我不知道这会对性能产生什么样的影

我有一个容器,里面有来自整个程序的指向double的指针,我有一个单独的线程“监视”这些double,并在它们更新时打印它们的最新值

如果其中一个双打快速改变两次(例如2->3->4),我不在乎错过3。如果其中一个更改了,然后又更改回来(例如,2->3->2),我也不在乎是否遗漏了整个更新。我唯一关心的是最终的值最终会被打印出来。这里主要关注的是,这对主程序的性能有绝对最小的影响,主程序总是以某种任意的顺序读取和写入这些双精度

下面我有一个我认为有效的示例程序,但我不知道这会对性能产生什么样的影响。我在玩具示例上做了一些基准测试,这似乎没有造成太大的伤害,但我担心我的玩具示例不会像大型程序那样产生效果。最坏情况下的响应延迟在这里是非常重要的,所以在我围绕它构建太多系统之前,我想确保这个机制是合理的

我真正感兴趣的是一点教育。为什么从不同的线程查看值会对性能产生任何影响?为了减轻这种影响,我进行了100毫秒的睡眠,但我可能缺少了一部分理论,那就是为什么它在一个有多个CPU的系统上很重要。是否有有趣的缓存效果

最后,是否有什么我应该改变,使显示器有较少的影响

提前感谢您的帮助

#include <iostream>
#include <thread>
#include <vector>
#include <string>

struct Record {
  volatile double& target_;
  double last_;
  std::string name_;
};

void monitor(std::vector<Record> records) {
  while (true) {
    for (auto& record : records) {
      double target = record.target_;
      if (record.last_ != target) {
        std::cout << record.name_ << ' ' << target << std::endl;
        record.last_ = target;
      }
    }

    // does this throttling help anything?
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }
}

int main() {
  std::vector<double> v(5, -1);

  std::vector<Record> records;
  for (size_t i = 0 ; i < v.size() ; ++i) {
    records.push_back({v[i], v[i], "v[" + std::to_string(i) + "]"});
  }
  std::thread(monitor, records).detach();

  for (size_t i = 0 ; i < v.size() ; ++i) {
    // Can the presence of the monitor slow down this write? (or a read?)
    v[i] = i; 
    std::this_thread::sleep_for(std::chrono::seconds(1));    
  }
}
#包括
#包括
#包括
#包括
结构记录{
易变双目标;
双末位;
std::字符串名称;
};
无效监视器(标准::向量记录){
while(true){
用于(自动记录:记录(&R){
双目标=record.target_u2;;
if(record.last!=目标){

std::cout从示例代码中,您应该注意几个性能/安全问题:

  • 由于监视器线程不应写入向量中的double值,请使用const double&引用而不是double&。此外,请删除'volatile'关键字,除非存储double的内存在代码外部修改,否则这不是您想要的

  • 遍历向量中的所有值以查找修改后的值是一种CPU密集且效率非常低的方法。基本上,您使用轮询而不是推送通知来让监视器线程知道值何时更改


使用条件变量在发生更改时唤醒监视器线程。下面是关于如何使用条件变量的教程-

从示例代码中,您应该注意几个性能/安全问题:

  • 由于监视器线程不应写入向量中的double值,请使用const double&引用而不是double&。此外,请删除'volatile'关键字,除非存储double的内存在代码外部修改,否则这不是您想要的

  • 遍历向量中的所有值以查找修改后的值是一种CPU密集且效率非常低的方法。基本上,您使用轮询而不是推送通知来让监视器线程知道值何时更改


使用条件变量在发生更改时唤醒监视器线程。这里有一个关于如何使用条件变量的教程-

阅读我的整个帖子,我在“实际问题的答案”下面有一些重要内容

读取另一个处理器(CPU、内核或任何你称之为它的东西)缓存中的数据的直接效果缓存内容必须传输到另一个CPU。这种传输的效率/低效率因处理器体系结构的不同而不同。在最坏的情况下,数据必须写入实际RAM,在其他情况下,数据进入某个中间缓存,或在某个内部“处理器间通信通道”上传输。总体效果可能是在“发送”处理器上刷新缓存项,但通常更好

写入同一缓存线(
last\ux
)也会通过刷新该缓存线而影响另一个处理器,因此,现在另一个处理器必须重新加载
目标值,即使它没有更改。同样,这“坏”的详细程度取决于确切的处理器体系结构,有时还取决于系统设计(例如,速度有多快,内存类型有多大)

但是,您的代码完全不是一般意义上的合理代码:

  • 读取/写入非原子数据的效果是不可预测的(如果不是未定义的话,至少是实现定义的行为),并且取决于实际的处理器体系结构。不能保证在您的示例中读取
    target\uuuu
    将以原子方式完成,因此您可能会得到随机垃圾作为“结果”。这可能会导致各种问题,包括应用程序崩溃,因为,例如,实际数据的两部分包含不一致的浮点数据,并导致处理器执行FP异常

  • 请注意,如果另一个线程正在更新
    记录
    向量,则
    for(auto&record:records){
    是不安全的-例如,它可能会重新分配整个向量内容,并且您当前的数据会被重新用于其他目的


阅读我的整个帖子,在“对你实际问题的回答”下面我有一些重要的东西

读取另一个处理器(CPU、core或任何你称之为它的处理器)缓存中的数据的直接影响是缓存内容必须传输到另一个CPU