packages feed

fast-mult (empty) → 0.1.0.0

raw patch · 6 files changed

+366/−0 lines, 6 filesdep +basedep +ghc-primdep +integer-gmpsetup-changed

Dependencies added: base, ghc-prim, integer-gmp, strict-base

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Clinton Mead (c) 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Clinton Mead nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,1 @@+# fast-mult
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ fast-mult.cabal view
@@ -0,0 +1,26 @@+name:                fast-mult+version:             0.1.0.0+synopsis:            Numeric type with asymptotically faster multiplications.+description:         This numeric type internally reorders multiplications to achieve+                     asymptotically faster multiplication of large numbers of small integers in particular.+                     See the module docs for more detail.+homepage:            https://github.com/clintonmead/fast-mult#readme+license:             BSD3+license-file:        LICENSE+author:              Clinton Mead+maintainer:          clintonmead@gmail.com+copyright:           Copyright: (c) 2017 Clinton Mead+category:            Web+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:     Data.FastMult, Data.FastMult.Internal+  build-depends:       base >= 4.9 && < 5, integer-gmp, ghc-prim, strict-base+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/clintonmead/fast-mult
+ src/Data/FastMult.hs view
@@ -0,0 +1,3 @@+module Data.FastMult (FastMult, FastMultSeq, simplify) where++import Data.FastMult.Internal
+ src/Data/FastMult/Internal.hs view
@@ -0,0 +1,304 @@+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE BangPatterns #-}++module Data.FastMult.Internal (FastMult(FastMult), FastMultSeq, simplify) where++#include "MachDeps.h"++import Prelude hiding (Integer)++import GHC.Integer.GMP.Internals (BigNat, Integer(S#, Jp#, Jn#), sizeofBigNat#, timesBigNat, bigNatToWord, wordToBigNat, wordToBigNat2)+import Data.Bits (FiniteBits, countLeadingZeros, finiteBitSize, xor, complement)+import GHC.Base (timesWord2#, Word(W#), int2Word#, Int(I#), eqWord#, (>=#), negateInt#)+import Data.Ord (comparing)+import Data.Strict.List ( List((:!)) )+import qualified Data.Strict.List as Strict+import GHC.TypeLits (Nat, KnownNat, natVal)+import GHC.Conc.Sync (par)+import Data.Proxy (Proxy)+import Data.Word (Word)+import Data.Ratio ((%))+import Data.Foldable (foldl')++-- We use 'Word' here not 'Bool' because it unpacks. I'm not sure if this is an optimisation.+newtype Sign = Sign Word deriving Show++pattern Pos = Sign 0+pattern Neg = Sign (-1)++multSigns :: Sign -> Sign -> Sign+multSigns (Sign x) (Sign y) = Sign (x `xor` y)++negateSign :: Sign -> Sign+negateSign (Sign x) = Sign (complement x)+++data BigNatWithScale (n :: Nat) where+  BigNatWithScale :: KnownNat n => {-# UNPACK #-} !Word -> BigNat -> BigNatWithScale n++-- Internal debug only:+instance Show (BigNatWithScale n) where+  show (BigNatWithScale scale bigNat) = "(BigNatWithScale scale = " ++ show scale ++ ", num = " ++ show (Jp# bigNat) ++ ")"++getBigNat :: BigNatWithScale (n :: Nat) -> BigNat+getBigNat (BigNatWithScale _ x) = x++{-|+  'FastMult' is a Numeric type that can be used in any place a 'Num a' is required.+  It represents a standard integer using three components, which multiplied together represent the stored number:++  1. The number's sign+  2. An unsigned machine word.+  3. A (possibly empty) list of 'BigNat's, which are the internal type for 'Integer's which are too large to fit in a machine word.++  Each 'BigNat' in the list has a scale. It's scale is the log base 2 of the number of words to store the machine word, minus 1.++  Note that we never store BigNats with length of only one machine word in this list, we instead convert them to an ordinary+  unsigned machine word and multiply them by item 2 in the list above. Only then if the result overflows we place them in this+  'BigNat' list.++  This is a few examples of "MachineWords -> Scale"++  2 -> 0+  3 -> 1+  4 -> 1+  5 -> 2+  6..8 -> 2+  9..16 -> 3+  17..32 -> 4++  etc.++  Note this "scale" has the very nice property that multipling 'BigNat's of scale @x@ always results in a 'BigNat' of scale @x+1@.++  The list of 'BigNat's only ever contains one 'BigNat' of each "scale". As the size of 'BigNat's increases exponentially with scale,+  this list should always be relatively small. The 'BigNat' list is always sorted as well, smallest to largest.++  When we multiply two 'FastMult's, we merge the BigNat lists. This is basically a simple merge of sorted list,+  but with one significant change. Note that we said that the 'BigNat' list cannot contain two 'BigNat's of the same scale.+  So if find that a 'BigNat' in the left hand list of the multiplication is the same scale as a 'BigNat' in right hand list,+  we multiply these two 'BigNat's to create a 'BigNat' one "scale" larger. We then continue the merge, including this new BigNat.++  As a result, we only ever multiply numbers of the same "scale", that is, no more than double the length of one another.++  Why do we do this? Well, an ordinary product, say @product [1..1000000]@, towards the end of the list involves multiplications+  of a very large number by a machine word. These take @O(n)@ time. So the whole product takes @O(n^2)@ time.++  If we instead did the following:++  @+    product x y = product x mid * product mid y+      mid = (x + y) `div` 2++    (suitible base case here)+  @++  We find that this runs a lot faster. The reason is that with this approach we're minimising products involving very large numbers,+  and importantly, multiplying two @n@ length numbers doesn't take @O(n^2)@ but more like @O(n*log(n))@ time.+  For this reason it's better to do a few multiplication of large numbers by large numbers,+  instead of lots of multiplications of large numbers by small numbers.++  But to do this I've had to redefine product. What if you don't want to change the algorithm, but just want to use one that's+  already been written, perhaps inefficiently. Well this is where 'FastMult' is useful. Instead of making the algorithm smarter,+  'FastMult' just makes numbers smarter. The numbers themselves reorder the multiplications so you don't have too.++  As well as having the advantage of speeding up existing algorithms, 'FastMult' dynamically behaves differently based on+  what numbers it's actually multiplying and always maintains the invariant that multiplications will not be performed between+  numbers greater than twice the size each other.++  At this point I haven't mentioned the meaning of the `FastMult` type parameter @n@'. 'FastMult' can also add paralellism to+  your multiplication algorithms. However, sparking new GHC threads has a cost, so we only want to do it for large multiplications.+  Multiplications of @scale > n@ will spark a new thread, so @n = 0@ will spark new threads for any multiplication+  involving at least 3 machine words. This is probably too small, you can experiment with different numbers.+  Note that @n@ represents the scale, not size, so for example setting @n=4@ will only spark threads for multiplications involving+  at least 33 machine words.++  How well parallelism works (or if it works at all) hasn't been tested yet however.++  We include an ordinary machine word in the type as an optimisation for single machine word numbers.+  This is because multiplying 'BigNat's involves calling GMP using a C call, which is a large overhead for small multiplications.++  To use 'FastMult', all you have to do is import it's type, not it's implementation.+  If you're not interested in parallelism, just import 'FastMultSeq'.++  For example, just compare in GHCi:++  @+  product [1..100000]+  @++  and:++  @+  product [1::FastMultSeq..100000]+  @++  and you should find the latter completes much faster.++  Converting to and from 'Integer's can be done with the+  'toInteger' and 'fromInteger' class methods from 'Integral' and 'Num' respectively.+-}+data FastMult (n :: Nat) where+  FastMult :: KnownNat n => {-# UNPACK #-} !Sign -> {-# UNPACK #-} !Word -> !(Strict.List (BigNatWithScale n)) -> FastMult n++{-|+  A type synonym for a fully sequential 'FastMult'. The parameter is supposed to be 'WORD_MAX', but I couldn't find that+  defined, anyway what's important is that anything of scale smaller than @0xFFFFFFFF@ will be sequential, which is everything.+-}+type FastMultSeq = FastMult 0xFFFFFFFF++data BigNatMultResult (n :: Nat) where+  ScaleLT :: BigNatMultResult n+  ScaleEQ :: (KnownNat n) => BigNatWithScale n -> BigNatMultResult n+  ScaleGT :: BigNatMultResult n++singletonStrictList :: a -> Strict.List a+singletonStrictList x = x :! Strict.Nil++instance KnownNat n => Eq (FastMult n) where+  x == y = toInteger x == toInteger y++instance KnownNat n => Ord (FastMult n) where+  x `compare` y = toInteger x `compare` toInteger y++instance KnownNat n => Enum (FastMult n) where+  toEnum = fromIntegral+  fromEnum = fromIntegral++instance KnownNat n => Num (FastMult n) where+  fromInteger = \case+    (S# prim_i) -> case (prim_i >=# 0#) of+      1# -> FastMult Pos (W# (int2Word# prim_i)) Strict.Nil+      0# -> FastMult Neg (W# (int2Word# (negateInt# prim_i))) Strict.Nil+    (Jp# x) -> fromBigNat Pos x+    (Jn# x) -> fromBigNat Neg x+    where+      fromBigNat :: Sign -> BigNat -> FastMult n+      fromBigNat sign x = case (W# (int2Word# (sizeofBigNat# x)) - 1) of+        0 -> FastMult sign (W# (bigNatToWord x)) Strict.Nil+        size -> FastMult sign 1 (singletonStrictList (BigNatWithScale (logBase2Int size) x))+      logBase2Int :: Word -> Word+      logBase2Int x = WORD_SIZE_IN_BITS - 1 - (fromIntegral (countLeadingZeros x))+  (FastMult s1 w1 l1) * (FastMult s2 w2 l2) =+    let+      multBigNatWithScale :: forall n. BigNatWithScale n -> BigNatWithScale n -> BigNatMultResult n+      multBigNatWithScale (BigNatWithScale s1 n1) (BigNatWithScale s2 n2) =+          case (s1 `compare` s2) of+            EQ -> result `seqOrPar` (ScaleEQ (BigNatWithScale (s1 + 1) result)) where+              result = n1 `timesBigNat` n2+              seqOrPar = if s1 <= maxSeq then seq else par+            LT -> ScaleLT+            GT -> ScaleGT+          where+            maxSeq = fromIntegral (natVal (undefined :: Proxy n))+      sr = multSigns s1 s2+      (# wu_prim, wl_prim #) =+        let+          !(W# w1_prim) = w1+          !(W# w2_prim) = w2+        in+          timesWord2# w1_prim w2_prim+      merge :: Strict.List (BigNatWithScale n) -> Strict.List (BigNatWithScale n) -> Strict.List (BigNatWithScale n)+      merge xl Strict.Nil = xl+      merge Strict.Nil yl = yl+      merge xl@(x:!xs) yl@(y:!ys) = case multBigNatWithScale x y of+        ScaleEQ result -> mergeWithCarry result xs ys+        ScaleLT -> x :! merge xs yl+        ScaleGT -> y :! merge xl ys++      mergeWithCarry :: BigNatWithScale n -> Strict.List (BigNatWithScale n) -> Strict.List (BigNatWithScale n) -> Strict.List (BigNatWithScale n)+      mergeWithCarry carry xl Strict.Nil = mergeOneCarry carry xl+      mergeWithCarry carry Strict.Nil yl = mergeOneCarry carry yl+      mergeWithCarry carry xl@(x:!xs) yl@(y:!ys) = case multBigNatWithScale x y of+        ScaleEQ result -> mergeWithCarry carry xs ys+        ScaleLT -> contCarry x xs yl+        ScaleGT -> contCarry y ys xl+        where+          contCarry x xs yl =+            case multBigNatWithScale carry x of+              ScaleEQ result -> mergeWithCarry result xs yl+              ScaleLT -> carry :! x :! merge xs yl+              ScaleGT -> error $+                "Carry should never be larger than first element. This should never happen. Report as bug.\n" +++                "Details:\n" +++                "carry =\n" +++                show carry ++ "\n" +++                "xl =\n" +++                show xl ++ "\n" +++                "yl =\n" +++                show yl ++ "\n"++      mergeOneCarry carry Strict.Nil = singletonStrictList carry+      mergeOneCarry carry xl@(x:!xs) = case multBigNatWithScale carry x of+        ScaleLT -> carry :! xl+        ScaleEQ result -> mergeOneCarry result xs+        ScaleGT -> error $+          "Carry should never be larger than first element. This should never happen. Report as bug.\n" +++          "Details:\n" +++          "carry =\n" +++          show carry ++ "\n" +++          "xl =\n" +++          show xl ++ "\n"+    in+      case eqWord# wu_prim (int2Word# 0#) of+        0# -> FastMult sr (W# wl_prim) (merge l1 l2)+        _  -> FastMult sr 1 (mergeWithCarry (BigNatWithScale 0 (wordToBigNat2 wu_prim wl_prim)) l1 l2)+  (+) = binaryViaInteger (+)+  (-) = binaryViaInteger (-)+  abs (FastMult _ word l) = FastMult Pos word l+  signum (FastMult Pos _ _) = FastMult Pos 1 Strict.Nil+  signum (FastMult Neg _ _) = FastMult Neg 1 Strict.Nil+  negate (FastMult sign word l) = FastMult (negateSign sign) word l++binaryViaInteger f x y = fromInteger (toInteger x `f` toInteger y)+unaryViaInteger f = fromInteger . f . toInteger++instance KnownNat n => Real (FastMult n) where+  toRational x = (toInteger x) % 1++instance KnownNat n => Integral (FastMult n) where+  toInteger (FastMult sign (W# word_prim) l) = case sign of+    Pos -> Jp# result+    Neg -> Jn# result+    where+      result = foldl' (\x y -> x `timesBigNat` getBigNat y) (wordToBigNat word_prim) l+  x `quotRem` y = let (x_r, y_r) = (toInteger x `quotRem` toInteger y) in (fromInteger x_r, fromInteger y_r)++instance KnownNat n => Show (FastMult n) where+  show = show . toInteger++instance KnownNat n => Read (FastMult n) where+  readsPrec p s = map (\(x,y) -> (fromInteger x,y)) (readsPrec p s)++{-|+  'simplify' returns a 'FastMult' the same as it's argument but "simplified".++  To explain this, consider the following for @x :: FastMult@:++  @+  f x = (show x, x + 1)+  @++  It will multiply out @x@ twice, once for the addition, and once for 'show'. Note that the list of 'BigInt's in @x@ is generally+  a small number, as only one 'BigInt' is stored for each scale, and the sizes of scales increase exponentially, but there+  may be some multiplications required nevertheless. A better way to write this is as follows:++  @+  f x = let y = simplify x in (show y, y + 1)+  @++  This will ensure that @x@ is multiplied out only once.++  Unfortunately using 'simplify' stops your algorithms from being generic,+  so it might be better to define simplify as 'id' with a rewrite rule. I'll think about this.+-}+simplify :: KnownNat n => FastMult n -> FastMult n+simplify = fromInteger . toInteger