为什么引入相关类型会影响我的表现?

时间:2014-05-21 20:24:16

标签: haskell type-families associated-types

在我的kdtree项目中,我根据Int中的Key a类型,将深度计数器从基于a - 基于显式KDTree v a替换为显式benchmarking nr/kdtree_nr mean: 60.19084 us, lb 59.87414 us, ub 60.57270 us, ci 0.950 std dev: 1.777527 us, lb 1.494657 us, ub 2.120168 us, ci 0.950 。这是diff

现在,虽然我认为这应该是类型级别的更改,但我的基准测试显示性能急剧下降:

在:

benchmarking nr/kdtree_nr 
mean: 556.9518 us, lb 554.0586 us, ub 560.6128 us, ci 0.950 
std dev: 16.70620 us, lb 13.58185 us, ub 20.63450 us, ci 0.950

后:

data Key a :: *

在我深入 Core 之前......任何人都知道这里发生了什么?

编辑1

正如Thomas(和userxyz)所建议的,我将type Key a :: *替换为benchmarking nr/kdtree_nr mean: 538.2789 us, lb 537.5128 us, ub 539.4408 us, ci 0.950 std dev: 4.745118 us, lb 3.454081 us, ub 6.969091 us, ci 0.950 并相应地更改了实现。这对结果没有任何重大影响:

lvl20 :: KDTree Vector (V3 Double) -> [V3 Double]
lvl20 =
  \ (w4 :: KDTree Vector (V3 Double)) ->
    $wpointsAround $fKDCompareV3_$s$fKDCompareV3 lvl2 lvl4 nrRadius q w4

编辑2

快速浏览一下Core输出。显然,这种改变会阻止依赖于类的函数专门化,对吧?

在:

lvl18 :: KDTree Vector (V3 Double) -> [V3 Double]
lvl18 =
  \ (w4 :: KDTree Vector (V3 Double)) ->
    $wpointsAround $dKDCompare lvl1 lvl3 nrRadius q w4

后:

src/Data/KDTree.hs:48:49:
    Could not deduce (k ~ KeyV3)
    from the context (Real a, Floating a)
      bound by the instance declaration at src/Data/KDTree.hs:45:10-49
    or from (Key k)
      bound by the type signature for
                 dimDistance :: Key k => k -> V3 a -> V3 a -> Double
      at src/Data/KDTree.hs:47:3-13
      ‘k’ is a rigid type variable bound by
          the type signature for
            dimDistance :: Key k => k -> V3 a -> V3 a -> Double
          at src/Data/KDTree.hs:47:3
    Relevant bindings include
      k :: k (bound at src/Data/KDTree.hs:47:15)
      dimDistance :: k -> V3 a -> V3 a -> Double
        (bound at src/Data/KDTree.hs:47:3)
    In the pattern: V3X
    In a case alternative: V3X -> ax - bx
    In the second argument of ‘($)’, namely
      ‘case k of {
         V3X -> ax - bx
         V3Y -> ay - by
         V3Z -> az - bz }’

编辑2的小更新:对 INLINE pragma 发疯并不会在这里改变一件事。

编辑3

快速实施 userxyz 建议的内容:http://lpaste.net/104457 曾经去过那里,不能让它发挥作用:

{{1}}

编辑4

嗯......我想我只是解决了#34;只需在函数中抛出 SPECIALIZE pragma即可解决问题。这实际上导致所有内联都被内联并删除显式字典传递。

我对这个解决方案不太满意,因为这意味着我必须放一个大的#34;请专注于你的电话以获得不错的表现"文档中的警告。

2 个答案:

答案 0 :(得分:1)

我只是偶然发现了这个问题:Transitivity of Auto-Specialization in GHC

OP引用了“来自the docs GHC 7.6:”(强调我的):

  

[Y]你通常甚至不需要 SPECIALIZE pragma。在编译模块M时,GHC的优化器(带-O)自动考虑在M中声明的每个顶级重载函数,并将其专门用于在M中调用它的不同类型。优化器还会考虑每个导入的 INLINABLE 重载函数,并将其专门用于在M中调用它的不同类型。

因此,我刚刚删除了所有(硬) INLINE SPECIALIZE pragma,并在适当的情况下将其替换为 INLINEABLE pragma(即on基准套件中使用的每个函数)。因此,与所有函数的内联编译指示相比,我获得了更好的时间。

Quintessence:让编译器完成它的工作,但有时会给他一个提示。

答案 1 :(得分:0)

这可能没有帮助,因为它没有解决真正的问题,即代码速度变慢,但您可以使用type代替datakFirstkSucc不起作用的原因是因为无法从应用程序中推断出a是什么,因此无法选择实例,因为实例取决于仅限a而非Key a。您可以通过为这些功能提供见证来解决此问题:

class KDCompare a where
  type Key a :: *

  kSuccWith :: proxy a -> Key a -> Key a
  kFirstWith :: proxy a -> Key a 

然后相应地修改你的功能:

kdtree :: (KDCompare a, G.Vector v a) => BucketSize -> v a -> KDTree v a
kdtree mb vs = ana (kdtreeF mb) (kFirstWith vs, vs) 

kdtreeF :: (KDCompare a, G.Vector v a) => BucketSize -> (Key a,v a) -> KDTreeF v a (Key a,v a)
kdtreeF (BucketSize mb) (k0, v0) = go (k0, v0)
  where go (k,fs) | G.length fs <= mb = LeafF (G.convert fs)
                  | otherwise         = NodeF k (G.head r) (kSucc' k,l) (kSucc' k,r)
                    where (l,r) = splitBuckets k fs
                          kSucc' = kSuccWith v0

分开KeyKDCompare可能更有意义:

class Enum a => Key a where 
  kSucc :: a -> a
  kFirst :: a

class KDCompare a where
  dimDistance  :: Key key => key -> a -> a -> Double
  realDistance :: a -> a -> Double

然后,您的数据类型必须通过密钥进行参数化:

data KDTree k v a = Node k a (KDTree k v a) (KDTree k v a)
                    | Leaf (v a)


data KDTreeF k v a f = NodeF k a f f | LeafF (v a)
  deriving (Functor)

但是你的功能可以更自然地写出来:

kdtree :: (Key key, KDCompare a, G.Vector v a) => BucketSize -> v a -> KDTree key v a
kdtree mb vs = ana (kdtreeF mb) (kFirst, vs)