简体   繁体   中英

Haskell strategies for speculative execution of conditionals / Alternative

I wonder which parallel strategy, spark, or whatever, can be used for speculative execution of a Haskell program that consists of a lot of conditional tests, recursively.

Assume a program that has a lot of conditional tests, recursively. I imagine that if sparks are used, then most sparks will be working on useless branches. The spark lifecycle does not include cancellation.

A workable strategy has to be able to efficiently create, but also cancel, units of work, in a dependency tree.

As an example, consider the problem of parsing a text. A parser consists of a huge tree of basically:

if <looks-like-1> then
   if <looks-like 1.1> then
     if <looks-like 1.1.1> then
       success
     else failure
   else if <looks-like 1.1> ...
else if ...

At a given conditional, we cannot know whether we will backtrack or not until much later. By speculatively executing the other branch, we can solve this problem faster.

However, if we just use sparks, there is an exponential increase in useless work and we won't get much of a speedup. There must be a way to cancel work that has started on a branch when we know that it will never be taken.

A generalizaiton of this would be speculative execution for any data type that implements Alternative , the idea being that cancellation of an alternative that is never observed doesn't change the semantics of a program. So a <|> b where b is not returned from the expression, can be short-circuited, say through throwing an exception during speculative execution, without affecting the semantics.

How would you approach this in Haskell?

If we can countenance abandoning the world of pure parallel computations, we can turn to the async package, which allows cancellation of asynchronous tasks.

For example, here's an speculative "if" that allows conditions that take a while to be calculated. It launches both branches concurrently, and when the result of the condition becomes known promptly kills the losing branch:

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE NumDecimals #-}
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async
import Control.Exception (onException)

iffy :: IO Bool -> IO a -> IO a -> IO a
iffy test left right =
    withAsync left \leftAsync ->
    withAsync right \rightAsync ->
        do testResult <- test
           let (shouldWait,shouldCancel) =
                    if testResult then (leftAsync,rightAsync)
                                  else (rightAsync,leftAsync)
           cancel shouldCancel
           wait shouldWait

iffyTest :: Bool -> IO Int
iffyTest b =
    iffy do threadDelay 1e6 >> pure b
         do (threadDelay 2e6 >> pure 5) `onException` putStrLn "cancelled L"
         do (threadDelay 2e6 >> pure 2) `onException` putStrLn "cancelled R"

Putting it to work:

λ iffyTest True
cancelled R
5
λ iffyTest False
cancelled L
2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM