2014-06-10 9 views
9

In this domanda qui su SO differenze tra i due operatori div e quot sono menzionati come pure il fatto che l'operatore quot è più efficiente rispetto all'operatore div, considerando div è più naturale per noi gli umani da usare.differenza esatta tra div e quot

La mia domanda è quali sono le esatte implementazioni dei due operatori e collegate a ciò che è la differenza tra le implementazioni. Voglio anche sapere come si presenta la differenza di velocità tra i due, dato che usare Hoogle e sfogliare le fonti non mi ha aiutato nella mia ricerca di comprensione.

Voglio chiarire che capisco la differenza generale tra i due operatori e sono interessato solo alle implementazioni o piuttosto alle differenze.

+0

Ho aggiunto anche un po 'di benchmarking, FYI. –

+0

@ AndrásKovács Grazie per l'ottima risposta! : D – ThreeFx

+0

Come @augustss menziona nella sua risposta alla domanda che hai collegato, la ragione * fondamentale * per la differenza di velocità è che "quot" è ciò che viene solitamente implementato direttamente come istruzione nelle moderne CPU. Quindi, come accennato in seguito, questo è ciò che GHC sceglie come sua operazione primitiva. –

risposta

9

quot giri verso zero, div arrotonda verso l'infinito negativo:

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

quanto riguarda l'overhead di div, quot ha un corrispondente primitive GHC operation, mentre div fa 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# 

Nella finale forme, entrambe le funzioni hanno un po 'di 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 

Ho anche fatto un po 'piccolo di microbenchmarking, ma deve essere assunto con una notevole quantità di sale, perché GHC e LLVM ottimizzare codice numerico stretta via come non c'è domani. Ho cercato di ostacolarli e i risultati sembrano essere realistici: 14,67 ms per div e 13,37 ms per quot. Inoltre, è GHC 7.8.2 con -O2 e -fllvm. Ecco il codice:

{-# 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] 
+0

Non so abbastanza sul benchmark per sapere se il tuo benchmark testa numeri negativi. Lo fa? – dfeuer

+0

@dfeuer è tutto positivo. Ho eseguito alcuni numeri negativi e i risultati erano simili, ma comunque penso che questo non sia un ottimo esempio di benchmarking; uno migliore sarebbe dove usiamo NOINLINE-s e guardiamo GHC Core per assicurarci che stiamo analizzando la cosa giusta. –

+0

La ragione per cui ho menzionato i negativi è che se GHC/LLVM è abbastanza intelligente da comprendere che ci sono solo aspetti positivi, allora svuoterà la logica 'divMod' dopo l'inlining. Probabilmente hai ragione che è necessario un benchmarking più attento, ma io * sicuramente * non è quello giusto per farlo. – dfeuer

Problemi correlati