在c++中实现进度可视化

Implementing progress visualization in C++

本文关键字:可视化 实现 c++      更新时间:2023-10-16

假设我有一个计算密集型算法正在运行。
例如,假设这是一个路由算法,并且在一个单独线程上运行的窗口上,我想向用户显示当前正在分析的路由等,并且由于某种原因,它包含大量cpu密集型代码。

重要的是,I 不想仅仅为了显示进度而减慢工作线程的速度;它需要尽可能地全速运行。如果用户看到过时的数据,例如实际上没有发生的中间数据(例如,同时出现两条活动路由),这是完全可以的,因为此进度可视化仅用于信息目的,而不是其他目的。

从理论的角度来看,我认为根据c++标准,我最好的选择是在两个线程上使用std::atomicstd::memory_order_relaxed。但是这会显著降低工作线程上的代码速度。

从实际的角度来看,我只是想完全忽略std::atomic,只是让工作线程正常地与所有变量一起工作。谁会关心GUI线程是否读取过时的数据?我不知道,想必用户也不会知道。实际上,这无关紧要,因为只有一个工作线程,并且只有该线程需要观察有效的写操作,这在实践中是唯一会发生的事情。

我想知道的是:
从理论上和实践上看,解决这类问题的最好办法是什么?
人们是忽略标准而去使用原始的原语,还是咬紧牙关接受使用std::atomic的性能损失?
还是有我不知道的其他设施可以解决这个问题?

忽略std::atomic的适当隔离不会为您带来匹配,但您可能有完全失去线程之间通信的风险,主要是在编译器端。例如,在x86硬件端根本不存在这个问题,因为每个存储到内存(如果您可以确保编译器按预期执行)无论如何都需要存储与发布语义。

我也怀疑分享进程的频率超过30-100 FPS(或Hz)会带来任何价值。另一方面,它肯定会给系统资源带来不必要的负担(如果在紧密循环中重复),并破坏编译器优化,例如向量化。

因此,如果关注工作线程的开销,那么共享信息的频率要低一些。例如,每1024次迭代更新一次原子计数器:

// worker thread
if( i%1024 == 0 ) // update the progress info
    my_atomic_progress.store( i, std::memory_order_release ); // regular `mov` on x86
// GUI thread
auto i = my_atomic_progress.load( std::memory_order_consume );

此示例还显示了建立通信所需的最小隔离,否则编译器可以自由地优化循环外的内存操作,例如

没有最好的方法-这取决于您需要发送多少数据到显示,如果它只是一个长整数值,并且显示是完全没有保证的,那么我只写值并完成它。偶尔阅读器会读到一个损坏的值,但这无关紧要,所以我不关心。

否则,我很想将值发送给队列,然后使用事件或条件变量触发读取(通常您不希望读取器全速运行,并且需要某种方式通知它有新数据要读取)

我不确定std::atomic的开销有那么大——它不是要在OS原语中实现吗?如果是这样,在编译器和优化器完成它们的工作后,原语(在Windows上,至少通过InterlockedExchange函数)最终作为单个CPU指令结束。