9

In this question here on SO the differences between the two operators div and quot are mentioned as well as the fact that the quot operator is more efficient than the div operator, whereas div is more natural for us humans to use.

My question is what the exact implementations of the two operators are and linked to that what the difference between implementations is. Also I want to know how the speed difference between those two comes to be, as using Hoogle and browsing the sources did not help me in my quest to understanding.

I want to clarify that I understand the general difference between the two operators and only am interested in the implementations or rather the differences.

ThreeFx
  • 7,250
  • 1
  • 27
  • 51
  • I added a tiny bit of benchmarking too, FYI. – András Kovács Jun 10 '14 at 21:00
  • As @augustss mentions in his answer to the question you linked, the *fundamental* reason for the speed difference is that `quot` is what is usually implemented directly as an instruction in modern CPUs. Thus, as mentioned below, that is what GHC chooses as its primitive operation. – Ørjan Johansen Jun 12 '14 at 22:24

1 Answers1

9

quot rounds towards zero, div rounds towards negative infinity:

div  (-3) 2 == (-2)
quot (-3) 2 == (-1)

As to the overhead of div, quot has a corresponding primitive GHC operation, while div does some extra work:

quotRemInt :: Int -> Int -> (Int, Int)
(I# x) `quotRemInt` (I# y) = case x `quotRemInt#` y of
                             (# q, r #) ->
                                 (I# q, I# r)

divModInt# :: Int# -> Int# -> (# Int#, Int# #)
x# `divModInt#` y#
 | (x# ># 0#) && (y# <# 0#) = case (x# -# 1#) `quotRemInt#` y# of
                              (# q, r #) -> (# q -# 1#, r +# y# +# 1# #)
 | (x# <# 0#) && (y# ># 0#) = case (x# +# 1#) `quotRemInt#` y# of
                              (# q, r #) -> (# q -# 1#, r +# y# -# 1# #)
 | otherwise                = x# `quotRemInt#` y#

In their final forms, both functions have some error handling checks on them:

a `quot` b
 | b == 0                     = divZeroError
 | b == (-1) && a == minBound = overflowError -- Note [Order of tests]
                                              -- in GHC.Int
 | otherwise                  =  a `quotInt` b

a `div` b
 | b == 0                     = divZeroError
 | b == (-1) && a == minBound = overflowError -- Note [Order of tests]
                                              -- in GHC.Int
 | otherwise                  =  a `divInt` b

I also did a very small bit of microbenchmarking, but it should be taken with a hefty amount of salt, because GHC and LLVM optimize tight numeric code away like there's no tomorrow. I tried to thwart them, and the results seem to be realistic: 14,67 ms for div and 13,37 ms for quot. Also, it's GHC 7.8.2 with -O2 and -fllvm. Here's the code:

{-# LANGUAGE BangPatterns #-}

import Criterion.Main
import System.Random

benchOp :: (Int -> Int) -> Int -> ()
benchOp f = go 0 0 where
    go !i !acc !limit | i < limit = go (i + 1) (f i) limit
                      | otherwise = ()

main = do
    limit1 <- randomRIO (1000000, 1000000 :: Int)
    limit2 <- randomRIO (1000000, 1000000 :: Int)
    n      <- randomRIO (100, 100 :: Int)
    defaultMain [
        bench "div"  $ whnf (benchOp (`div`  n)) limit1,
        bench "quot" $ whnf (benchOp (`quot` n)) limit2]
András Kovács
  • 29,931
  • 3
  • 53
  • 99
  • I don't know enough about benchmarking to tell whether your benchmark tests negative numbers. Does it? – dfeuer Jun 13 '14 at 21:55
  • @dfeuer it's all positive. I ran some with negative numbers, and the results were similar, but anyway I think this is not a very good piece of benchmarking; a better one would be where we use NOINLINE-s and look at the GHC Core to make sure we're benchmarking the right thing. – András Kovács Jun 14 '14 at 07:51
  • The reason I mentioned the negatives is that if GHC/LLVM is smart enough to realize there are only positives, then it will gut the `divMod` logic after inlining. You're probably right that more careful benchmarking is needed, but I'm *definitely* not the right one to do that. – dfeuer Jun 14 '14 at 13:05