【问题标题】:C++ How to make timer accurate in LinuxC++如何在Linux中使计时器准确
【发布时间】:2017-10-07 20:37:11
【问题描述】:

考虑这段代码:

#include <iostream>
#include <vector>
#include <functional>
#include <map>
#include <atomic>
#include <memory>
#include <chrono>
#include <thread>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/asio/high_resolution_timer.hpp>

static const uint32_t FREQUENCY = 5000; // Hz
static const uint32_t MKSEC_IN_SEC = 1000000;

std::chrono::microseconds timeout(MKSEC_IN_SEC / FREQUENCY);
boost::asio::io_service ioservice;
boost::asio::high_resolution_timer timer(ioservice);

static std::chrono::system_clock::time_point lastCallTime = std::chrono::high_resolution_clock::now();
static uint64_t deviationSum = 0;
static uint64_t deviationMin = 100000000;
static uint64_t deviationMax = 0;
static uint32_t counter = 0;

void timerCallback(const boost::system::error_code &err) {
  auto actualTimeout = std::chrono::high_resolution_clock::now() - lastCallTime;
  std::chrono::microseconds actualTimeoutMkSec = std::chrono::duration_cast<std::chrono::microseconds>(actualTimeout);
  long timeoutDeviation = actualTimeoutMkSec.count() - timeout.count();
  deviationSum += abs(timeoutDeviation);
  if(abs(timeoutDeviation) > deviationMax) {
    deviationMax = abs(timeoutDeviation);
  } else if(abs(timeoutDeviation) < deviationMin) {
    deviationMin = abs(timeoutDeviation);
  }

  ++counter;
  //std::cout << "Actual timeout: " << actualTimeoutMkSec.count() << "\t\tDeviation: " << timeoutDeviation << "\t\tCounter: " << counter << std::endl;

  timer.expires_from_now(timeout);
  timer.async_wait(timerCallback);
  lastCallTime = std::chrono::high_resolution_clock::now();
}

using namespace std::chrono_literals;

int main() {
  std::cout << "Frequency: " << FREQUENCY << " Hz" << std::endl;
  std::cout << "Callback should be called each: " << timeout.count() << " mkSec" << std::endl;
  std::cout << std::endl;

  ioservice.reset();
  timer.expires_from_now(timeout);
  timer.async_wait(timerCallback);
  lastCallTime = std::chrono::high_resolution_clock::now();
  auto thread = new std::thread([&] { ioservice.run(); });
  std::this_thread::sleep_for(1s);

  std::cout << std::endl << "Messages posted: " << counter << std::endl;
  std::cout << "Frequency deviation: " << FREQUENCY - counter << std::endl;
  std::cout << "Min timeout deviation: " << deviationMin << std::endl;
  std::cout << "Max timeout deviation: " << deviationMax << std::endl;
  std::cout << "Avg timeout deviation: " << deviationSum / counter << std::endl;

  return 0;
}

它运行计时器以指定频率定期调用 timerCallback(..)。在此示例中,回调必须每秒调用 5000 次。可以使用频率来查看实际(测量的)呼叫频率与期望的频率不同。事实上,频率越高,偏差越大。我做了一些不同频率的测量,总结如下: https://docs.google.com/spreadsheets/d/1SQtg2slNv-9VPdgS0RD4yKRnyDK1ijKrjVz7BBMSg24/edit?usp=sharing

当所需频率为 10000Hz 时,系统会错过 10% (~ 1000) 的呼叫。 当所需频率为 100000Hz 时,系统会错过 40% (~ 40000) 的呼叫。

问题:是否有可能在Linux\C++环境下达到更好的精度?怎么样?我需要它以 500000Hz 的频率工作而没有明显偏差

附:我的第一个想法是它是 timerCallback(..) 方法的主体本身导致延迟。我测量了它。稳定的执行时间不到 1 微秒。所以不影响进程。

【问题讨论】:

  • 不要使用std::chrono::high_resolution_clockstd::chrono::system_clock 使用std::chrono::steady_clock
  • 刚试过。偏差甚至比 high_resolution_clock 给出的还要高。
  • 高分辨率时钟只是一个稳定时钟的类型定义。查看。 This video 了解更多关于你应该在高分辨率上使用 stable_clock 的原因以及关于 chrono 的更多信息。
  • 如果您希望每秒被呼叫特定次数,为什么要这样做:timer.expires_from_now(timeout);?这意味着如果一个呼叫晚了一微秒,您将设置下一个呼叫晚一微秒。如果您有目标频率,那就错了。
  • @mc.android.developer 我怀疑你测量它的方式是无效的。例如,它是否考虑到所有缓存都是冷的这一事实?您可能正在将 CPU 从睡眠中唤醒?

标签: c++ linux timer


【解决方案1】:

我自己在这个问题上没有经验,但我猜(正如参考资料所解释的那样)操作系统的调度程序会以某种方式干扰您的回调。 因此,您可以尝试使用实时调度程序并尝试将任务的优先级更改为更高的。

希望这能给你找到答案的方向。

调度器: http://gumstix.8.x6.nabble.com/High-resolution-periodic-task-on-overo-td4968642.html

优先级: https://linux.die.net/man/3/setpriority

【讨论】:

    【解决方案2】:

    如果您需要每两微秒间隔一次调用,您最好附加到绝对时间位置,并且不要考虑每个请求将需要的时间......虽然您遇到了问题每个时隙所需的处理可能比它执行所需的时间更多。

    如果您有一个多核 cpu,我会在每个核心之间划分时间段(以多线程方法),以便每个核心更长,所以假设您在四核 cpu 中有您的要求,那么您可以允许每个线程每 8usec 执行 1 cal,这可能更实惠。在这种情况下,您使用绝对计时器(一个绝对计时器是一个等待挂钟滴答特定绝对时间的计时器,而不是从您调用它的时间开始的延迟)并将它们偏移量等于线程数 2usec延迟,在这种情况下(4 核),您将在时间 T 启动线程 #1,在时间 T + 2usec 启动线程 #2,在时间 T + 4usec 启动线程 #3,...并在时间 T + 2 * 启动线程 #N (N-1) 微秒。然后每个线程将在 oldT + 2usec 时间再次启动,而不是进行某种nsleep(3) 调用。这不会累积延迟呼叫的处理时间,因为这很可能是您正在经历的。 pthread 库计时器都是绝对时间计时器,因此您可以使用它们。我认为这是您能够达到如此严格的规格的唯一方法。 (并准备看看电池如何受到影响,假设你在一个 android 环境中)

    注意

    在这种方法中,外部总线可能是一个瓶颈,所以即使你让它工作,可能最好用NTP同步几台机器(这可以在usec级别完成,以实际GBit的速度链接)并使用并行运行的不同处理器。由于您没有描述必须如此密集地重复的任何过程,因此我无法为该问题提供更多帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-18
      • 2015-07-10
      相关资源
      最近更新 更多