Skip to content

Commit 652ee91

Browse files
committed
perf: implement fast Get for integral types
This patch implements fast `Get` logic for integral types based on: - Use a single load operation when loading with same endianness of the host, otherwise do a host load and a byteSwap. This avoids the overhead of multiple single-byte loads in the previous implementation. - Use the unaligned Addr# load/store primops added since GHC 9.10 when available, otherwise do a plain peek. This ensures the GHC backends see the right AlignmentSpec at the Cmm level and can correctly emit unaligned load instructions. There's no need for changing `Put` logic they're backed by `FixedPrim` logic in `Data.ByteString.Builder.Prim.Binary` that already does similar optimization.
1 parent 245f989 commit 652ee91

1 file changed

Lines changed: 154 additions & 73 deletions

File tree

src/Data/Binary/Get.hs

Lines changed: 154 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
{-# LANGUAGE CPP, RankNTypes, MagicHash, BangPatterns #-}
22
{-# LANGUAGE Trustworthy #-}
3+
{-# LANGUAGE UnboxedTuples #-}
34

45
#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)
56
#include "MachDeps.h"
67
#endif
78

9+
#if defined(MIN_VERSION_GLASGOW_HASKELL) && MIN_VERSION_GLASGOW_HASKELL(9,10,0,0)
10+
#define HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE
11+
#endif
12+
813
-----------------------------------------------------------------------------
914
-- |
1015
-- Module : Data.Binary.Get
@@ -234,6 +239,13 @@ import qualified Data.Binary.Get.Internal as I
234239
-- needed for casting words to float/double
235240
import Data.Binary.FloatCast (wordToFloat, wordToDouble)
236241

242+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
243+
import GHC.Exts
244+
import GHC.IO
245+
import GHC.Int
246+
import GHC.Word
247+
#endif
248+
237249
-- $lazyinterface
238250
-- The lazy interface consumes a single lazy 'L.ByteString'. It's the easiest
239251
-- interface to get started with, but it doesn't support interleaving I\/O and
@@ -426,9 +438,11 @@ getRemainingLazyByteString = withInputChunks () consumeAll L.fromChunks resumeOn
426438
-- helper, get a raw Ptr onto a strict ByteString copied out of the
427439
-- underlying lazy byteString.
428440

441+
#if !defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
429442
getPtr :: Storable a => Int -> Get a
430443
getPtr n = readNWith n peek
431444
{-# INLINE getPtr #-}
445+
#endif
432446

433447
-- | Read a Word8 from the monad state
434448
getWord8 :: Get Word8
@@ -444,125 +458,116 @@ getInt8 = fromIntegral <$> getWord8
444458
-- force GHC to inline getWordXX
445459
{-# RULES
446460
"getWord8/readN" getWord8 = readN 1 B.unsafeHead
447-
"getWord16be/readN" getWord16be = readN 2 word16be
448-
"getWord16le/readN" getWord16le = readN 2 word16le
449-
"getWord32be/readN" getWord32be = readN 4 word32be
450-
"getWord32le/readN" getWord32le = readN 4 word32le
451-
"getWord64be/readN" getWord64be = readN 8 word64be
452-
"getWord64le/readN" getWord64le = readN 8 word64le #-}
461+
#-}
453462

454463
-- | Read a Word16 in big endian format
455464
getWord16be :: Get Word16
456-
getWord16be = readN 2 word16be
457-
458-
word16be :: B.ByteString -> Word16
459-
word16be = \s ->
460-
(fromIntegral (s `B.unsafeIndex` 0) `unsafeShiftL` 8) .|.
461-
(fromIntegral (s `B.unsafeIndex` 1))
462-
{-# INLINE[2] getWord16be #-}
463-
{-# INLINE word16be #-}
465+
#if defined(WORDS_BIGENDIAN)
466+
getWord16be = getWord16host
467+
#else
468+
getWord16be = byteSwap16 <$> getWord16host
469+
#endif
470+
{-# INLINE getWord16be #-}
464471

465472
-- | Read a Word16 in little endian format
466473
getWord16le :: Get Word16
467-
getWord16le = readN 2 word16le
468-
469-
word16le :: B.ByteString -> Word16
470-
word16le = \s ->
471-
(fromIntegral (s `B.unsafeIndex` 1) `unsafeShiftL` 8) .|.
472-
(fromIntegral (s `B.unsafeIndex` 0) )
473-
{-# INLINE[2] getWord16le #-}
474-
{-# INLINE word16le #-}
474+
#if defined(WORDS_BIGENDIAN)
475+
getWord16le = byteSwap16 <$> getWord16host
476+
#else
477+
getWord16le = getWord16host
478+
#endif
479+
{-# INLINE getWord16le #-}
475480

476481
-- | Read a Word32 in big endian format
477482
getWord32be :: Get Word32
478-
getWord32be = readN 4 word32be
479-
480-
word32be :: B.ByteString -> Word32
481-
word32be = \s ->
482-
(fromIntegral (s `B.unsafeIndex` 0) `unsafeShiftL` 24) .|.
483-
(fromIntegral (s `B.unsafeIndex` 1) `unsafeShiftL` 16) .|.
484-
(fromIntegral (s `B.unsafeIndex` 2) `unsafeShiftL` 8) .|.
485-
(fromIntegral (s `B.unsafeIndex` 3) )
486-
{-# INLINE[2] getWord32be #-}
487-
{-# INLINE word32be #-}
483+
#if defined(WORDS_BIGENDIAN)
484+
getWord32be = getWord32host
485+
#else
486+
getWord32be = byteSwap32 <$> getWord32host
487+
#endif
488+
{-# INLINE getWord32be #-}
488489

489490
-- | Read a Word32 in little endian format
490491
getWord32le :: Get Word32
491-
getWord32le = readN 4 word32le
492-
493-
word32le :: B.ByteString -> Word32
494-
word32le = \s ->
495-
(fromIntegral (s `B.unsafeIndex` 3) `unsafeShiftL` 24) .|.
496-
(fromIntegral (s `B.unsafeIndex` 2) `unsafeShiftL` 16) .|.
497-
(fromIntegral (s `B.unsafeIndex` 1) `unsafeShiftL` 8) .|.
498-
(fromIntegral (s `B.unsafeIndex` 0) )
499-
{-# INLINE[2] getWord32le #-}
500-
{-# INLINE word32le #-}
492+
#if defined(WORDS_BIGENDIAN)
493+
getWord32le = byteSwap32 <$> getWord32host
494+
#else
495+
getWord32le = getWord32host
496+
#endif
497+
{-# INLINE getWord32le #-}
501498

502499
-- | Read a Word64 in big endian format
503500
getWord64be :: Get Word64
504-
getWord64be = readN 8 word64be
505-
506-
word64be :: B.ByteString -> Word64
507-
word64be = \s ->
508-
(fromIntegral (s `B.unsafeIndex` 0) `unsafeShiftL` 56) .|.
509-
(fromIntegral (s `B.unsafeIndex` 1) `unsafeShiftL` 48) .|.
510-
(fromIntegral (s `B.unsafeIndex` 2) `unsafeShiftL` 40) .|.
511-
(fromIntegral (s `B.unsafeIndex` 3) `unsafeShiftL` 32) .|.
512-
(fromIntegral (s `B.unsafeIndex` 4) `unsafeShiftL` 24) .|.
513-
(fromIntegral (s `B.unsafeIndex` 5) `unsafeShiftL` 16) .|.
514-
(fromIntegral (s `B.unsafeIndex` 6) `unsafeShiftL` 8) .|.
515-
(fromIntegral (s `B.unsafeIndex` 7) )
516-
{-# INLINE[2] getWord64be #-}
517-
{-# INLINE word64be #-}
501+
#if defined(WORDS_BIGENDIAN)
502+
getWord64be = getWord64host
503+
#else
504+
getWord64be = byteSwap64 <$> getWord64host
505+
#endif
506+
{-# INLINE getWord64be #-}
518507

519508
-- | Read a Word64 in little endian format
520509
getWord64le :: Get Word64
521-
getWord64le = readN 8 word64le
522-
523-
word64le :: B.ByteString -> Word64
524-
word64le = \s ->
525-
(fromIntegral (s `B.unsafeIndex` 7) `unsafeShiftL` 56) .|.
526-
(fromIntegral (s `B.unsafeIndex` 6) `unsafeShiftL` 48) .|.
527-
(fromIntegral (s `B.unsafeIndex` 5) `unsafeShiftL` 40) .|.
528-
(fromIntegral (s `B.unsafeIndex` 4) `unsafeShiftL` 32) .|.
529-
(fromIntegral (s `B.unsafeIndex` 3) `unsafeShiftL` 24) .|.
530-
(fromIntegral (s `B.unsafeIndex` 2) `unsafeShiftL` 16) .|.
531-
(fromIntegral (s `B.unsafeIndex` 1) `unsafeShiftL` 8) .|.
532-
(fromIntegral (s `B.unsafeIndex` 0) )
533-
{-# INLINE[2] getWord64le #-}
534-
{-# INLINE word64le #-}
510+
#if defined(WORDS_BIGENDIAN)
511+
getWord64le = byteSwap64 <$> getWord64host
512+
#else
513+
getWord64le = getWord64host
514+
#endif
515+
{-# INLINE getWord64le #-}
535516

536517

537518
-- | Read an Int16 in big endian format.
538519
getInt16be :: Get Int16
520+
#if defined(WORDS_BIGENDIAN)
521+
getInt16be = getInt16host
522+
#else
539523
getInt16be = fromIntegral <$> getWord16be
524+
#endif
540525
{-# INLINE getInt16be #-}
541526

542527
-- | Read an Int32 in big endian format.
543528
getInt32be :: Get Int32
529+
#if defined(WORDS_BIGENDIAN)
530+
getInt32be = getInt32host
531+
#else
544532
getInt32be = fromIntegral <$> getWord32be
533+
#endif
545534
{-# INLINE getInt32be #-}
546535

547536
-- | Read an Int64 in big endian format.
548537
getInt64be :: Get Int64
538+
#if defined(WORDS_BIGENDIAN)
539+
getInt64be = getInt64host
540+
#else
549541
getInt64be = fromIntegral <$> getWord64be
542+
#endif
550543
{-# INLINE getInt64be #-}
551544

552545

553546
-- | Read an Int16 in little endian format.
554547
getInt16le :: Get Int16
548+
#if defined(WORDS_BIGENDIAN)
555549
getInt16le = fromIntegral <$> getWord16le
550+
#else
551+
getInt16le = getInt16host
552+
#endif
556553
{-# INLINE getInt16le #-}
557554

558555
-- | Read an Int32 in little endian format.
559556
getInt32le :: Get Int32
557+
#if defined(WORDS_BIGENDIAN)
560558
getInt32le = fromIntegral <$> getWord32le
559+
#else
560+
getInt32le = getInt32host
561+
#endif
561562
{-# INLINE getInt32le #-}
562563

563564
-- | Read an Int64 in little endian format.
564565
getInt64le :: Get Int64
566+
#if defined(WORDS_BIGENDIAN)
565567
getInt64le = fromIntegral <$> getWord64le
568+
#else
569+
getInt64le = getInt64host
570+
#endif
566571
{-# INLINE getInt64le #-}
567572

568573

@@ -573,43 +578,91 @@ getInt64le = fromIntegral <$> getWord64le
573578
-- host order, host endian form, for the machine you're on. On a 64 bit
574579
-- machine the Word is an 8 byte value, on a 32 bit machine, 4 bytes.
575580
getWordhost :: Get Word
581+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
582+
getWordhost = readNWith SIZEOF_HSWORD $ \(Ptr p#) ->
583+
IO $ \s -> case readWord8OffAddrAsWord# p# 0# s of
584+
(# s', w# #) -> (# s', W# w# #)
585+
#else
576586
getWordhost = getPtr (sizeOf (undefined :: Word))
587+
#endif
577588
{-# INLINE getWordhost #-}
578589

579590
-- | /O(1)./ Read a 2 byte Word16 in native host order and host endianness.
580-
getWord16host :: Get Word16
581-
getWord16host = getPtr (sizeOf (undefined :: Word16))
591+
getWord16host :: Get Word16
592+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
593+
getWord16host = readNWith 2 $ \(Ptr p#) ->
594+
IO $ \s -> case readWord8OffAddrAsWord16# p# 0# s of
595+
(# s', w16# #) -> (# s', W16# w16# #)
596+
#else
597+
getWord16host = getPtr (sizeOf (undefined :: Word16))
598+
#endif
582599
{-# INLINE getWord16host #-}
583600

584601
-- | /O(1)./ Read a Word32 in native host order and host endianness.
585-
getWord32host :: Get Word32
602+
getWord32host :: Get Word32
603+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
604+
getWord32host = readNWith 4 $ \(Ptr p#) ->
605+
IO $ \s -> case readWord8OffAddrAsWord32# p# 0# s of
606+
(# s', w32# #) -> (# s', W32# w32# #)
607+
#else
586608
getWord32host = getPtr (sizeOf (undefined :: Word32))
609+
#endif
587610
{-# INLINE getWord32host #-}
588611

589612
-- | /O(1)./ Read a Word64 in native host order and host endianness.
590613
getWord64host :: Get Word64
614+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
615+
getWord64host = readNWith 8 $ \(Ptr p#) ->
616+
IO $ \s -> case readWord8OffAddrAsWord64# p# 0# s of
617+
(# s', w64# #) -> (# s', W64# w64# #)
618+
#else
591619
getWord64host = getPtr (sizeOf (undefined :: Word64))
620+
#endif
592621
{-# INLINE getWord64host #-}
593622

594623
-- | /O(1)./ Read a single native machine word in native host
595624
-- order. It works in the same way as 'getWordhost'.
596625
getInthost :: Get Int
626+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
627+
getInthost = readNWith SIZEOF_HSINT $ \(Ptr p#) ->
628+
IO $ \s -> case readWord8OffAddrAsInt# p# 0# s of
629+
(# s', i# #) -> (# s', I# i# #)
630+
#else
597631
getInthost = getPtr (sizeOf (undefined :: Int))
632+
#endif
598633
{-# INLINE getInthost #-}
599634

600635
-- | /O(1)./ Read a 2 byte Int16 in native host order and host endianness.
601636
getInt16host :: Get Int16
637+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
638+
getInt16host = readNWith 2 $ \(Ptr p#) ->
639+
IO $ \s -> case readWord8OffAddrAsInt16# p# 0# s of
640+
(# s', i16# #) -> (# s', I16# i16# #)
641+
#else
602642
getInt16host = getPtr (sizeOf (undefined :: Int16))
643+
#endif
603644
{-# INLINE getInt16host #-}
604645

605646
-- | /O(1)./ Read an Int32 in native host order and host endianness.
606647
getInt32host :: Get Int32
648+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
649+
getInt32host = readNWith 4 $ \(Ptr p#) ->
650+
IO $ \s -> case readWord8OffAddrAsInt32# p# 0# s of
651+
(# s', i32# #) -> (# s', I32# i32# #)
652+
#else
607653
getInt32host = getPtr (sizeOf (undefined :: Int32))
654+
#endif
608655
{-# INLINE getInt32host #-}
609656

610657
-- | /O(1)./ Read an Int64 in native host order and host endianness.
611658
getInt64host :: Get Int64
659+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
660+
getInt64host = readNWith 8 $ \(Ptr p#) ->
661+
IO $ \s -> case readWord8OffAddrAsInt64# p# 0# s of
662+
(# s', i64# #) -> (# s', I64# i64# #)
663+
#else
612664
getInt64host = getPtr (sizeOf (undefined :: Int64))
665+
#endif
613666
{-# INLINE getInt64host #-}
614667

615668

@@ -618,30 +671,58 @@ getInt64host = getPtr (sizeOf (undefined :: Int64))
618671

619672
-- | Read a 'Float' in big endian IEEE-754 format.
620673
getFloatbe :: Get Float
674+
#if defined(WORDS_BIGENDIAN)
675+
getFloatbe = getFloathost
676+
#else
621677
getFloatbe = wordToFloat <$> getWord32be
678+
#endif
622679
{-# INLINE getFloatbe #-}
623680

624681
-- | Read a 'Float' in little endian IEEE-754 format.
625682
getFloatle :: Get Float
683+
#if defined(WORDS_BIGENDIAN)
626684
getFloatle = wordToFloat <$> getWord32le
685+
#else
686+
getFloatle = getFloathost
687+
#endif
627688
{-# INLINE getFloatle #-}
628689

629690
-- | Read a 'Float' in IEEE-754 format and host endian.
630691
getFloathost :: Get Float
692+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
693+
getFloathost = readNWith 4 $ \(Ptr p#) ->
694+
IO $ \s -> case readWord8OffAddrAsFloat# p# 0# s of
695+
(# s', f# #) -> (# s', F# f# #)
696+
#else
631697
getFloathost = wordToFloat <$> getWord32host
698+
#endif
632699
{-# INLINE getFloathost #-}
633700

634701
-- | Read a 'Double' in big endian IEEE-754 format.
635702
getDoublebe :: Get Double
703+
#if defined(WORDS_BIGENDIAN)
704+
getDoublebe = getDoublehost
705+
#else
636706
getDoublebe = wordToDouble <$> getWord64be
707+
#endif
637708
{-# INLINE getDoublebe #-}
638709

639710
-- | Read a 'Double' in little endian IEEE-754 format.
640711
getDoublele :: Get Double
712+
#if defined(WORDS_BIGENDIAN)
641713
getDoublele = wordToDouble <$> getWord64le
714+
#else
715+
getDoublele = getDoublehost
716+
#endif
642717
{-# INLINE getDoublele #-}
643718

644719
-- | Read a 'Double' in IEEE-754 format and host endian.
645720
getDoublehost :: Get Double
721+
#if defined(HS_UNALIGNED_ADDR_PRIMOPS_AVAILABLE)
722+
getDoublehost = readNWith 8 $ \(Ptr p#) ->
723+
IO $ \s -> case readWord8OffAddrAsDouble# p# 0# s of
724+
(# s', d# #) -> (# s', D# d# #)
725+
#else
646726
getDoublehost = wordToDouble <$> getWord64host
727+
#endif
647728
{-# INLINE getDoublehost #-}

0 commit comments

Comments
 (0)