GHC专业化保证

时间:2017-04-12 14:45:48

标签: haskell ghc

我试图确保GHC专门化一个递归函数,以便所有内容都被取消装箱。 this gist中提供了完整的示例代码(以及GHC核心的转储)。有问题的功能如下:

import Data.Bits                                     
import qualified Data.Vector.Unboxed as UV

lookupSorted :: Ord a => (Int -> a) -> Int -> a -> Maybe Int   
lookupSorted lookupIx len needle =                  
  let !r = go 0 (len - 1)                   
   in if r < 0 then Nothing else Just r                    
  where                                     
  go :: Int -> Int -> Int                 
  go !lo !hi = if lo <= hi   
    then                                                
      let !mid = lo + (unsafeShiftR (hi - lo) 1)              
          !val = lookupIx mid                    
       in case compare val needle of                           
            EQ -> mid                                               
            LT -> go (mid + 1) hi                                              
            GT -> go lo (mid - 1)                                
    else (-1)               

它是一种算法,可以从任何已编入索引的容器中查找值。我想要确保的两个功能是:

{-# NOINLINE lookupUnboxedWord #-}
lookupUnboxedWord :: UV.Vector Word -> Word -> Maybe Int   
lookupUnboxedWord v w = lookupSorted (UV.unsafeIndex v) (UV.length v) w

{-# NOINLINE lookupUnboxedDouble #-}           
lookupUnboxedDouble :: UV.Vector Double -> Double -> Maybe Int   
lookupUnboxedDouble v w = lookupSorted (UV.unsafeIndex v) (UV.length v) w

好消息是,通过查看the dumped core,我可以看到GHC已经在进行我感兴趣的专业化。这非常令人印象深刻。但是,我希望能够指望它发生。我担心如果我在这个文件中添加足够的专用变体,或者如果我从另一个模块调用lookupSorted,那么GHC可能最终倾向于使用一个小的生成的可执行文件而不是快速的可执行文件。

我的理解是SPECIALIZE pragma对这种情况没有帮助。目前GHC does not allow specialization based on value arguments。我非常确定如果我愿意为索引操作编写类型类,那么我可以让SPECIALIZE工作。我试图避免使用这种方法,因为除非没有其他解决方案,否则我不想引入类型类。

有没有办法强制GHC创建我的功能的这些专门变体?此外,如果任何人对转储的核心文件有任何评论(如果某些内容不是最佳的),我将不胜感激。感谢。

---- ---- EDIT

考虑到这一点,似乎只需在INLINE上添加lookupSorted编译指示即可。 GHC文档不清楚INLINEletwhere子句中的递归绑定之间的交互。对此有任何澄清,希望有一个来源支持,可能会有所帮助。

1 个答案:

答案 0 :(得分:2)

你的最终观察结果是正确的:如果你在一个函数上放了一个INLINE注释,当有足够的参数调用它时它将被内联。

足够的参数表示=左侧函数的参数个数(与右侧的lambdas相对)。这允许你做像

这样的事情
foo op n  = \y -> go n y
  where go acc i = … op … 

fooSpec1 = foo (+) 0 
fooSpec2 = foo (*) 1

并获得foo的两个特化,然后您可以多次调用它们而无需进一步重复代码。

对于所有这些,where中发生的事情无关紧要,递归函数只会与foo内联。

(对不起,没有消息来支持。)