使用Repa并行阵列的习惯性选项定价和风险

假设我想使用有限差分方法为看涨期权定价并进行修正,那么下面的工作就是:

import Data.Array.Repa as Repa

r, sigma, k, t, xMax, deltaX, deltaT :: Double
m, n, p :: Int
r = 0.05
sigma = 0.2
k = 50.0
t = 3.0
m = 3
p = 1
xMax = 150
deltaX = xMax / (fromIntegral m)
n = 800
deltaT = t / (fromIntegral n)

singleUpdater a = traverse a id f
  where
    Z :. m = extent a
    f _get (Z :. ix) | ix == 0   = 0.0
    f _get (Z :. ix) | ix == m-1 = xMax - k
    f  get (Z :. ix)             = a * get (Z :. ix-1) +
                                   b * get (Z :. ix) +
                                   c * get (Z :. ix+1)
      where
        a = deltaT * (sigma^2 * (fromIntegral ix)^2 - r * (fromIntegral ix)) / 2
        b = 1 - deltaT * (r  + sigma^2 * (fromIntegral ix)^2)
        c = deltaT * (sigma^2 * (fromIntegral ix)^2 + r * (fromIntegral ix)) / 2

priceAtT :: Array U DIM1 Double
priceAtT = fromListUnboxed (Z :. m+1) [max 0 (deltaX * (fromIntegral j) - k) | j <- [0..m]]

testSingle :: IO (Array U DIM1 Double)
testSingle = computeP $ singleUpdater priceAtT 

但是现在假设我想要平行定价(比如做一个现货阶梯),那么我可以这样做:

multiUpdater a = fromFunction (extent a) f
     where
       f :: DIM2 -> Double
       f (Z :. ix :. jx) = (singleUpdater x)!(Z :. ix)
         where
           x :: Array D DIM1 Double
           x = slice a (Any :. jx)

priceAtTMulti :: Array U DIM2 Double
priceAtTMulti = fromListUnboxed (Z :. m+1 :. p+1)
                [max 0 (deltaX * (fromIntegral j) - k) | j <- [0..m], _l <- [0..p]]

testMulti :: IO (Array U DIM2 Double)
testMulti = computeP $ multiUpdater priceAtTMulti

问题:

  • 有没有更习惯于这样做的习惯方式?
  • 上述方法实际上是否会同时进行价格平衡
  • 我如何确定我的代码是否真的在生成将并行执行的东西?
  • 我尝试了3,但可悲地遇到了ghc中的一个错误:

    bash-3.2$ ghc -fext-core --make Test.hs
    [1 of 1] Compiling Main             ( Test.hs, Test.o )
    ghc: panic! (the 'impossible' happened)
     (GHC version 7.4.1 for x86_64-apple-darwin):
        MkExternalCore died: make_lit
    

    你的bug与你的代码无关 - 你使用-fext-core来打印外部核心格式的编译输出。 只是不这样做(看到核心,我使用ghc核心)。

    编译-O2-threaded

    $ ghc -O2 -rtsopts --make A.hs -threaded 
    [1 of 1] Compiling Main             ( A.hs, A.o )
    Linking A ...
    

    然后使用+RTS -N4运行,例如,使用4个线程:

    $ time ./A +RTS -N4
    [0.0,0.0,8.4375e-3,8.4375e-3,50.009375,50.009375,100.0,100.0]
    ./A  0.00s user 0.00s system 85% cpu 0.008 total
    

    因此,看到结果太快了。 我会将你的mp参数增加到1k和3k

    $ time ./A +RTS -N2
    ./A +RTS -N2  3.03s user 1.33s system 159% cpu 2.735 total
    

    所以是的,它并行运行。 第一次尝试时,在2核心机器上运行1.6倍。 另一个问题是它是否有效。 使用+ RTS - 你可以看到运行时统计信息:

    任务:4(1名有约束力,3名高峰工人(共3名),使用-N2)

    所以我们有3个线程并行运行(2个大概是算法,一个是IO管理器)。

    您可以通过调整GC设置来缩短运行时间。 例如,通过使用-A我们可以减少GC开销,并产生真正的并行加速。

    $ time ./A +RTS -N1 -A100M   
    ./A +RTS -N1 -A100M  1.99s user 0.29s system 99% cpu 2.287 total
    
    $ time ./A +RTS -N2 -A100M   
    ./A +RTS -N2 -A100M  2.30s user 0.86s system 147% cpu 2.145 total
    

    有时可以通过使用LLVM后端来提高数字性能。 这似乎也是这种情况:

    $ ghc -O2 -rtsopts --make A.hs -threaded -fforce-recomp -fllvm
    [1 of 1] Compiling Main             ( A.hs, A.o )
    Linking A ...
    
    $ time ./A +RTS -N2 -A100M                                    
    ./A +RTS -N2 -A100M  2.09s user 0.95s system 147% cpu 2.065 total
    

    没有什么特别的,但是你正在改善单线程版本的运行时间,并且我没有以任何方式修改你的原始代码。 要真正改善事情,您需要进行配置和优化。

    重新访问-A标志,我们可以在最初的线程分配区域使用更高的限制来进一步降低时间。

    $ ghc -Odph -rtsopts --make A.hs -threaded -fforce-recomp -fllvm
    
    $ time ./A +RTS -N2 -A60M -s
    ./A +RTS -N2 -A60M 1.99s user 0.73s system 144% cpu 1.880 total
    

    因此,通过使用并行运行时,LLVM后端,并小心使用GC标志,从2.7降低到1.8(改进30%)。 您可以查看GC旗标表面以找到最佳值:

    在这里输入图像描述

    随着-A64 -N2周围的波谷适合数据集大小。

    我还会强烈考虑在内核中使用手工通用子表达式消除,以避免过度重新计算。

    正如Alp所建议的那样,要查看程序的运行时行为,请编译threadscope(来自Hackage)并按如下所示运行:

    $ ghc -O2 -fllvm -rtsopts -threaded -eventlog --make A.hs
    
    $ ./A +RTS -ls -N2 -A60M
    

    你可以得到你的两个核心的事件追踪,如下所示:

    那么这里发生了什么? 您有一个初始阶段(0.8s)的设置时间 - 分配您的大列表并将其编码到一个重新排列的阵列中 - 正如您通过单线程插入GC和执行所看到的那样。 然后,在您的实际并行工作发生在最后300毫秒之前,单个内核上还有另外0.8秒的内容。

    因此,尽管您的实际算法可能会很好地并行化,但是所有周围的测试设置基本上都会影响结果。 如果我们序列化数据集,然后从磁盘加载它,我们可以得到更好的行为:

    $ time ./A +RTS -N2 -A60M
    ./A +RTS -N2 -A60M  1.76s user 0.25s system 186% cpu 1.073 total
    

    现在你的个人资料看起来更健康:

    在这里输入图像描述

    这看起来很棒! 非常少的GC(98.9%的生产力),和我的两个核心并行运行愉快。

    所以,最后,我们可以看到你有很好的平行度:

    1核心,1.855s

    $ time ./A +RTS -N1 -A25M
    ./A +RTS -N1 -A25M  1.75s user 0.11s system 100% cpu 1.855 total
    

    和2个核心,1.014s

    $ time ./A +RTS -N2 -A25M   
    ./A +RTS -N2 -A25M  1.78s user 0.13s system 188% cpu 1.014 total
    

    现在,具体回答你的问题:

  • 有没有更习惯于这样做的习惯方式?
  • 一般来说,修复代码应该由平行的旅行,消费者和生产以及可嵌入的内核函数组成。 所以只要你这样做,那么代码可能是惯用的。 如果有疑问,请看教程。 通常我会将你的工作内核(如f )标记为内联。

  • 上述方法实际上是否会同时进行价格平衡
  • 如果您使用computeP或各种贴图和折叠等并行组合器,代码将并行执行。 所以是的,它应该并行运行。

  • 我如何确定我的代码是否真的在生成将并行执行的东西?
  • 一般来说,你会先知道,因为你使用并行操作。 如果有疑问,运行代码并观察加速。 您可能需要优化代码。

    链接地址: http://www.djcxy.com/p/59975.html

    上一篇: Idiomatic option pricing and risk using Repa parallel arrays

    下一篇: Parallel mapM on Repa arrays