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 |