Skip to content

Optimize remainders by powers of two for Integer and Natural

It appears that GHC does not optimise even (n :: Integer) to a bitwise check, as it does for Int and Word (#14437 (closed)). Here is a benchmark:

import Data.Time.Clock

evenRem :: Integer -> Bool
evenRem n = fromIntegral n `rem` 2 == (0 :: Word)

lim :: Integer
lim = 100000000

main :: IO ()
main = do
  t0 <- getCurrentTime
  print $ maximum $ filter even [1..lim]
  t1 <- getCurrentTime
  putStrLn "even"
  print $ diffUTCTime t1 t0

  t0 <- getCurrentTime
  print $ maximum $ filter evenRem [1..lim]
  t1 <- getCurrentTime
  putStrLn "evenRem"
  print $ diffUTCTime t1 t0
$ ghc -O2 Even.hs
[1 of 1] Compiling Main             ( Even.hs, Even.o )
Linking Even ...
$ ./Even
100000000
even
6.367393s
100000000
evenRem
4.35664s

The reason is that even (n :: Integer) results in remInteger call in CMM, which remains unoptimized.

One possible solution is to add a special case of divisor 2 to GHC.Integer.Type.remInteger. Or perhaps even something like

remInteger n (S# d#)
  | isPowerOfTwo (I# d#) = fromIntegral (fromIntegral n `rem` I# d#)

isPowerOfTwo :: Int -> Bool
isPowerOfTwo d = d /= 0 && d .&. (complement d + 1) == d

Since remInteger is not inlined during Core pipeline, such implementation of even will pattern-match in runtime, which is a bit suboptimal. On my machine it benchmarks halfway between even and evenRem above.

Alternative approach is to add new rules to PrelRules.builtinIntegerRules to avoid any possible runtime slowdown. Is is appropriate?

Trac metadata
Trac field Value
Version 8.4.3
Type FeatureRequest
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component Core Libraries
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system
Architecture
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information