如何强制每秒进行X次评估?

Jam*_*ter 6 haskell

我需要一个实时的事件循环,它执行以下操作:

myEventLoop = do
    businessLogic x0 x1 x2 ... xN
    sleep 0.01
    myEventLoop
Run Code Online (Sandbox Code Playgroud)

我知道eventloop包,但它似乎更适合Web应用程序,而我需要的是实时信号处理。假设采样率为100 Hz。采样率不需要非常精确。如果偶尔只有98 Hz,那没什么大不了的。但是我永远不希望它偏离5%以上。目前,我们仅谈论GHC执行+/- 5%。忽略由于操作系统而导致的计时错误。

我的问题是:1)Haskell可以做这种事情吗?我有点担心+/- 5%的部分。2)睡眠部分效率低下吗?还是在myEventLoop执行睡眠部分时其他Haskell线程可以占用相同的OS线程?3)假设睡眠是一个好主意,硬编码睡眠时间很可能不是。由于程序在x86架构上运行,因此我可以读取时间戳计数器(TSC),但是如果GHC决定将程序切成微线程,将其中的一些发送到1个CPU内核,而将其他的发送到另一个CPU内核,则该方法可能会出现问题。 ,因为不同的核心可以具有不同的TSC值。这是我应该担心的事情吗?如果是,什么是更好的方法?

提前致谢

Tho*_*son 4

你可以尝试,但我并不乐观。GC 和 RTS 将以显着的负面方式影响你,但我不可能说你是否会在意。

Eventloop ......巨大......我个人会做一些定制和轻量级的事情。

不要使用“先睡眠然后逻辑”设置,因为“逻辑”部分不是免费的,并且您最终会得到比预期更长的暂停时间。我的“分叉逻辑然后睡眠”测试显示了类似的不良行为,因为sleep直到 RTS 再次调度父线程后才会启动。

使用绝对时间的定制解决方案以避免漂移

自定义解决方案如下所示:

import Control.Concurrent
import Control.Monad
import Data.Time

periodic :: NominalDiffTime -> UTCTime -> IO () -> IO ()
periodic delayTime base operation =
  do start <- getCurrentTime
     let end = addUTCTime delayTime base
         microSeconds = floor $ (1000000 :: Double) * realToFrac (diffUTCTime end start)
     threadDelay microSeconds
     void $ forkIO operation
     periodic delayTime end operation

main :: IO ()
main =
  do start <- getCurrentTime
     forkIO $ periodic (1/98) start (getCurrentTime >>= print)
     threadDelay 10000000 -- 10 seconds
Run Code Online (Sandbox Code Playgroud)

这很简单,并且可以让您真正接近任何框架所提供的功能,除非您需要一些时髦的功能,例如取消事件。您也将接近您想要的经期:

import Control.Concurrent
import Control.Monad
import Data.Time

periodic :: NominalDiffTime -> UTCTime -> IO () -> IO ()
periodic delayTime base operation =
  do start <- getCurrentTime
     let end = addUTCTime delayTime base
         microSeconds = floor $ (1000000 :: Double) * realToFrac (diffUTCTime end start)
     threadDelay microSeconds
     void $ forkIO operation
     periodic delayTime end operation

main :: IO ()
main =
  do start <- getCurrentTime
     forkIO $ periodic (1/98) start (getCurrentTime >>= print)
     threadDelay 10000000 -- 10 seconds
Run Code Online (Sandbox Code Playgroud)

结果是:

% ./y | wc -l
980
Run Code Online (Sandbox Code Playgroud)

您可以分析打印的时间来获得置信区间,但随着负载变化、堆增长以及 GC 需要进行更多暂停,情况会变得更糟。

实际答案

编辑因为您有实际问题。

1)Haskell 能做这种事吗?

这并不是 Haskell 真正的用途——GHC RTS 甚至不适合音频等软实时工作。

2)睡眠部分效率低下吗?或者当 myEventLoop 执行睡眠部分时,其他 Haskell 线程可以占用同一个操作系统线程吗?

多个 Haskell 线程可以占用同一个操作系统线程,没有问题。

3)假设睡觉是个好主意,那么对睡眠时间进行硬编码很可能不是。我可以读取时间戳计数器 (TSC),因为程序在 x86 架构上运行,但如果 GHC 决定将程序分割成微线程,将一些发送到 1 个 CPU 核心,将其他发送到不同的 CPU 核心,那么这种方法可能会出现问题,因为不同的核心可以有不同的 TSC 值。这是我应该担心的事情吗?如果是,更好的方法是什么?

是的,您应该担心 Haskell 线程从一个核心移动到另一个核心,以及从一个操作系统线程移动到另一个操作系统线程。以上是我对本土方法的尝试。十一年前,我control-event(在 hackage 上)写了一篇文章来为我解决同样的问题,但我的时间限制不是什么问题,需要一点不同。如今,您可以直接使用 GHC 事件管理器,但我会避免使用它,因为 API 级别较低,并且它不能解决 GC 暂停的根本问题。