on master help sign in
Fix the Eq instance of Array

Byte comparison may not be correct for all types.

commit23750f38a8c9b6ab27d7ce8453436f6398002523 authorHarendra Kumar <harendra@composewell.com> dateTue 26 Dec 2023, 11:43 (3 years ago) parent51d99c9
2 files changed+90−12
core/src/Streamly/Internal/Data/Array/Type.hs+64−6 core/src/Streamly/Internal/Data/MutArray/Type.hs+26−6
core/src/Streamly/Internal/Data/Array/Type.hs+64−6
@@ -84,6 +84,8 @@ module Streamly.Internal.Data.Array.Type
8484 , write
8585 , pinnedWrite
8686 , unsafeMakePure
87+ , byteCmp
88+ , byteEq
8789
8890 -- ** Streams of arrays
8991 , chunksOf
@@ -104,8 +106,9 @@ import Control.Exception (assert)
104106import Control.Monad (replicateM)
105107import Control.Monad.IO.Class (MonadIO(..))
106108import Data.Functor.Identity (Identity(..))
109+import Data.Int (Int8, Int16, Int32, Int64)
107110import Data.Proxy (Proxy(..))
108−import Data.Word (Word8)
111+import Data.Word (Word8, Word16, Word32, Word64)
109112import GHC.Base (build)
110113import GHC.Exts (IsList, IsString(..), Addr#)
111114
@@ -254,7 +257,10 @@ isPinned = MA.isPinned . unsafeThaw
254257-- Construction
255258-------------------------------------------------------------------------------
256259
257−-- Splice two immutable arrays creating a new array.
260+-- | Copy two immutable arrays into a new array. If you want to splice more
261+-- than two arrays then this operation would be highly inefficient because it
262+-- would make a copy on every splice operation, instead use the
263+-- 'fromArrayStreamK' operation to combine n immutable arrays.
258264{-# INLINE splice #-}
259265splice :: (MonadIO m, Unbox a) => Array a -> Array a -> m (Array a)
260266splice arr1 arr2 =
@@ -687,12 +693,64 @@ instance Unbox a => IsList (Array a) where
687693 {-# INLINE toList #-}
688694 toList = toList
689695
690−-- XXX we are assuming that Unboxed equality means element equality. This may
691−-- or may not be correct? arrcmp is 40% faster compared to stream equality.
692−instance (Unbox a, Eq a) => Eq (Array a) where
696+-- | Byte compare two arrays. Compare the length of the arrays. If the length
697+-- is equal, compare the lexicographical ordering of two underlying byte arrays
698+-- otherwise return the result of length comparison.
699+--
700+-- /Unsafe/: Note that the 'Unbox' instance of sum types with constructors of
701+-- different sizes may leave some memory uninitialized which can make byte
702+-- comparison unreliable.
703+--
704+-- /Pre-release/
705+{-# INLINE byteCmp #-}
706+byteCmp :: Array a -> Array a -> Ordering
707+byteCmp arr1 arr2 =
708+ unsafeInlineIO $! unsafeThaw arr1 `MA.byteCmp` unsafeThaw arr2
709+
710+-- | Byte equality of two arrays.
711+--
712+-- >>> byteEq arr1 arr2 = (==) EQ $ Array.byteCmp arr1 arr2
713+--
714+-- /Unsafe/: See 'byteCmp'.
715+{-# INLINE byteEq #-}
716+byteEq :: Array a -> Array a -> Bool
717+byteEq arr1 arr2 = (==) EQ $ byteCmp arr1 arr2
718+
719+#define MK_EQ_INSTANCE(typ) \
720+instance {-# OVERLAPPING #-} Eq (Array typ) where { \
721+; {-# INLINE (==) #-} \
722+; (==) = byteEq \
723+}
724+
725+MK_EQ_INSTANCE(Char)
726+MK_EQ_INSTANCE(Word8)
727+MK_EQ_INSTANCE(Word16)
728+MK_EQ_INSTANCE(Word32)
729+MK_EQ_INSTANCE(Word64)
730+MK_EQ_INSTANCE(Int)
731+MK_EQ_INSTANCE(Int8)
732+MK_EQ_INSTANCE(Int16)
733+MK_EQ_INSTANCE(Int32)
734+MK_EQ_INSTANCE(Int64)
735+
736+-- | If the type allows a byte-by-byte comparison (see 'byteCmp') this instance
737+-- can be overlapped by a more specific instance that uses a byte comparison.
738+-- Byte comparison can be significantly faster.
739+--
740+-- For example:
741+--
742+-- >>> (==) = byteCmp
743+--
744+instance {-# OVERLAPPABLE #-} (Unbox a, Eq a) => Eq (Array a) where
693745 {-# INLINE (==) #-}
694746 arr1 == arr2 =
695− (==) EQ $ unsafeInlineIO $! unsafeThaw arr1 `MA.cmp` unsafeThaw arr2
747+ -- Does unboxed byte equality mean element equality?
748+ -- XXX This is incorrect for sum types, as we may have some
749+ -- uninitialized memory in that case. If we always initialize the
750+ -- unused memory to zero we can use this.
751+ -- Byte comparison is 40% faster compared to stream equality.
752+ -- (==) EQ $ unsafeInlineIO $! unsafeThaw arr1 `MA.cmp` unsafeThaw arr2
753+ (toStreamD arr1 :: Stream Identity a) == toStreamD arr2
696754
697755instance (Unbox a, Ord a) => Ord (Array a) where
698756 {-# INLINE compare #-}
core/src/Streamly/Internal/Data/MutArray/Type.hs+26−6
@@ -181,6 +181,8 @@ module Streamly.Internal.Data.MutArray.Type
181181 , foldl'
182182 , foldr
183183 , cmp
184+ , byteCmp
185+ , byteEq
184186
185187 -- ** Arrays of arrays
186188 -- We can add dimensionality parameter to the array type to get
@@ -2424,14 +2426,18 @@ asUnpinnedPtrUnsafe arr f =
24242426-- Equality
24252427-------------------------------------------------------------------------------
24262428
2427−-- | Compare the length of the arrays. If the length is equal, compare the
2428−-- lexicographical ordering of two underlying byte arrays otherwise return the
2429−-- result of length comparison.
2429+-- | Byte compare two arrays. Compare the length of the arrays. If the length
2430+-- is equal, compare the lexicographical ordering of two underlying byte arrays
2431+-- otherwise return the result of length comparison.
2432+--
2433+-- /Unsafe/: Note that the 'Unbox' instance of sum types with constructors of
2434+-- different sizes may leave some memory uninitialized which can make byte
2435+-- comparison unreliable.
24302436--
24312437-- /Pre-release/
2432−{-# INLINE cmp #-}
2433−cmp :: MonadIO m => MutArray a -> MutArray a -> m Ordering
2434−cmp arr1 arr2 =
2438+{-# INLINE byteCmp #-}
2439+byteCmp :: MonadIO m => MutArray a -> MutArray a -> m Ordering
2440+byteCmp arr1 arr2 =
24352441 liftIO
24362442 $ do
24372443 let marr1 = getMutableByteArray# (arrContents arr1)
@@ -2454,6 +2460,20 @@ cmp arr1 arr2 =
24542460 return $ compare r 0
24552461 x -> return x
24562462
2463+{-# INLINE cmp #-}
2464+{-# DEPRECATED cmp "Please use byteCmp instead." #-}
2465+cmp :: MonadIO m => MutArray a -> MutArray a -> m Ordering
2466+cmp = byteCmp
2467+
2468+-- | Byte equality of two arrays.
2469+--
2470+-- >>> byteEq arr1 arr2 = (==) EQ $ MArray.byteCmp arr1 arr2
2471+--
2472+-- /Unsafe/: See 'byteCmp'.
2473+{-# INLINE byteEq #-}
2474+byteEq :: MonadIO m => MutArray a -> MutArray a -> m Bool
2475+byteEq arr1 arr2 = fmap (EQ ==) $ byteCmp arr1 arr2
2476+
24572477-------------------------------------------------------------------------------
24582478-- NFData
24592479-------------------------------------------------------------------------------