Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fmap (fmap f) (stepper a b) performs differently to stepper (f a) (fmap f b) #139

Open
ocharles opened this issue Aug 31, 2016 · 3 comments

Comments

@ocharles
Copy link
Collaborator

We have the following code in our application:

      currentFrame <-
        let undistort' img = undistort img cameraMatrix distCoeffs
        in fmap (fmap undistort')
                (stepper (appImage initialAppData) newFrameAcquired)

undistort is a relatively expensive operation, taking around 0.3s. Therefore it's important that we only distort when we really need to - which is exactly when a new frame comes in (from a web camera). However, the above seems to result in undistort being evaluated whenever I need the value of the currentFrame Behavior. The following rewrite evaluates undistort only when necessary:

      currentFrame <-
        let undistort' img = undistort img cameraMatrix distCoeffs
        in stepper (undistort' (appImage initialAppData)) (fmap undistort' newFrameAcquired)

Is this to be expected?

@HeinrichApfelmus
Copy link
Owner

It does look undesirable to me that the two code snippets have different performance characteristics.

At the moment, the internal implementation of reactive-banana uses different mechanisms for memoizing Event compared to memoizing Behavior values. The latter is not quite as good as the former, hence the discrepancy. That said, I'm not entirely sure why it doesn't work in this case.

@mitchellwrosen
Copy link
Collaborator

I was unable to reproduce this with the following code:

import Control.Monad
import Reactive.Banana
import Reactive.Banana.Frameworks
import System.IO.Unsafe
import Control.Concurrent

main :: IO ()
main = do
  (ah, fire) <- newAddHandler
  (ah2, fire2) <- newAddHandler

  (actuate =<<) . compile $ do
    eEnter <- fromAddHandler ah
    eFrame <- fromAddHandler ah2

    bNum <- (fmap.fmap) expensive (stepper 0 eFrame)

    reactimate (putStrLn "frame" <$ eFrame)
    reactimate (print <$> bNum <@ eEnter)

  void . forkIO . forM_ [1..] $ \i -> do
    threadDelay (3*1000*1000)
    fire2 i

  forever $ getLine >> fire ()

expensive :: Int -> Int
expensive n =
  unsafePerformIO $ do
    putStrLn "expensive calculation!"
    threadDelay (1*1000*1000)
    pure (n*2)
{-# NOINLINE expensive #-}

Running this, "frames" arrive every 3 seconds, and pressing enter prints the value of the behavior that is updated by an expensive pure function every frame.

If I run this and press enter a bunch, I only see the expensive operation computed once per new frame, and between frames, it's cached.

@ocharles
Copy link
Collaborator Author

ocharles commented Sep 4, 2022

May be a duplicate of #251

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants