Type family reduction irregularity (change from 7.10.3 to 8.0.1)
I was recently made aware that vinyl's performance dramatically deteriorated with GHC 8.0.1 when compared to GHC 7.x. Vinyl is an extensible records library that's been around for about four years; the aspect of its design relevant here is the basic HList-style indexed GADT. Care was taken in the past so that working with, say, a Storable
Vector
of records would suffer no overhead from vinyl
when compared with standard records, and we have a benchmark suite to spot check this.
In the move to GHC-8.0.1, it turns out that we do introduce overhead. Inspecting the Core reveals that the benchmark inner loop includes a case
match on an HEq_sc
value whose presence I would not expect, and that is not present when compiling with GHC-7.10.3:
case HEq_sc
@ Nat
@ Nat
@ ('S 'Z)
@ (RIndex
'("normal", V3 Float)
'['("tex", V2 Float), '("normal", V3 Float)])
($s$fRElemar:S_$s$fRElemar:S_$cp1RElem
`cast` (N:~[0] <Nat>_N <'S 'Z>_N <RIndex
'("normal",
V3 Float)
'['("tex",
V2 Float),
'("normal",
V3 Float)]>_N
:: (('S 'Z :: Nat)
~
(RIndex
'("normal", V3 Float)
'['("tex", V2 Float),
'("normal",
V3 Float)] :: Nat) :: Constraint)
~R#
(('S 'Z :: Nat)
~~
(RIndex
'("normal", V3 Float)
'['("tex", V2 Float),
'("normal",
V3 Float)] :: Nat) :: Constraint)))
of cobox0 { __DEFAULT ->
I have since made an effort to reproduce the issue, and discovered more fragility than I expected. I am attaching two modules: Rec.hs
defines a kind of record type, Main.hs
is a test program that I will reproduce here,
{-# LANGUAGE DataKinds #-}
module Main where
import Rec
type MyRec = Rec '[ '("A",Int), '("B",Int), '("C",Int) ]
getC :: MyRec -> Int
getC = getField (Proxy::Proxy '("C",Int))
doubleC :: MyRec -> MyRec
doubleC r = setC (2 * (getC r)) r
where setC = set . (Field :: Int -> Field '("C",Int))
main :: IO ()
main = print (getC (Field 1 :& Field 2 :& Field 3 :& Nil :: MyRec))
If the doubleC
definition is present, the Core emitted (with -O2
) includes an HEq_sc
case in the RHS of getC
. If doubleC
is commented out, that case HEq_sc ...
is no longer present. In this example, the offending piece of Core is,
case HEq_sc
@ Nat
@ Nat
@ (Index '("C", Int) '['("B", Int), '("C", Int)])
@ ('S 'Z)
($s$fHasFieldkr:S_$s$fHasFieldkr:S_$cp1HasField
`cast` (N:~[0] <Nat>_N <Index
'("C", Int) '['("B", Int), '("C", Int)]>_N <'S 'Z>_N
:: ((Index '("C", Int) '['("B", Int), '("C", Int)] :: Nat)
~
('S 'Z :: Nat) :: Constraint)
~R#
((Index '("C", Int) '['("B", Int), '("C", Int)] :: Nat)
~~
('S 'Z :: Nat) :: Constraint)))
of cobox1 { __DEFAULT ->
If the contents of Rec.hs
are included in Main.hs
, the case HEq_sc ...
is not present in the resulting Core.
The result of what looks like a failure to normalize the Index
type family (or RIndex
in vinyl
) manifests as a 2x slowdown in the benchmark available in the vinyl
repository.
Trac metadata
Trac field | Value |
---|---|
Version | 8.0.1 |
Type | Bug |
TypeOfFailure | OtherFailure |
Priority | normal |
Resolution | Unresolved |
Component | Compiler |
Test case | |
Differential revisions | |
BlockedBy | |
Related | |
Blocking | |
CC | |
Operating system | |
Architecture |