on master help sign in
Portable native UTF-8 decoder gives 3.7x faster decoding
efa8732Bryan O'Sullivan
Data/Text/Encoding.hs+35−44
@@ -1,4 +1,5 @@
1−{-# LANGUAGE BangPatterns #-}
1+{-# LANGUAGE BangPatterns, ForeignFunctionInterface, MagicHash,
2+ UnliftedFFITypes #-}
2-- |
3-- |
3-- Module : Data.Text.Encoding
4-- Module : Data.Text.Encoding
4-- Copyright : (c) 2008, 2009 Tom Harper,
5-- Copyright : (c) 2008, 2009 Tom Harper,
@@ -47,23 +48,25 @@ module Data.Text.Encoding
47 ) where
48 ) where
48
49
49import Control.Exception (evaluate, try)
50import Control.Exception (evaluate, try)
51+import Control.Monad.ST (unsafeIOToST, unsafeSTToIO)
50import Data.Bits ((.&.))
52import Data.Bits ((.&.))
51import Data.ByteString as B
53import Data.ByteString as B
52import Data.ByteString.Internal as B
54import Data.ByteString.Internal as B
53−import Data.ByteString.Unsafe as B
54import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
55import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
55import Data.Text.Internal (Text(..), textP)
56import Data.Text.Internal (Text(..), textP)
56import Data.Text.UnsafeChar (ord, unsafeWrite)
57import Data.Text.UnsafeChar (ord, unsafeWrite)
57import Data.Text.UnsafeShift (shiftL, shiftR)
58import Data.Text.UnsafeShift (shiftL, shiftR)
58import Data.Word (Word8)
59import Data.Word (Word8)
60+import Foreign.C.Types (CSize)
59import Foreign.ForeignPtr (withForeignPtr)
61import Foreign.ForeignPtr (withForeignPtr)
60−import Foreign.Ptr (plusPtr)
62+import Foreign.Marshal.Utils (with)
63+import Foreign.Ptr (Ptr, plusPtr)
61−import Foreign.Storable (poke)
64+import Foreign.Storable (peek, poke)
65+import GHC.Base (MutableByteArray#)
62import System.IO.Unsafe (unsafePerformIO)
66import System.IO.Unsafe (unsafePerformIO)
63import qualified Data.Text.Array as A
67import qualified Data.Text.Array as A
64import qualified Data.Text.Encoding.Fusion as E
68import qualified Data.Text.Encoding.Fusion as E
65import qualified Data.Text.Encoding.Utf16 as U16
69import qualified Data.Text.Encoding.Utf16 as U16
66−import qualified Data.Text.Encoding.Utf8 as U8
67import qualified Data.Text.Fusion as F
70import qualified Data.Text.Fusion as F
68
71
69-- $strict
72-- $strict
@@ -86,47 +89,31 @@ decodeASCII bs = F.unstream (E.streamASCII bs)
86
89
87-- | Decode a 'ByteString' containing UTF-8 encoded text.
90-- | Decode a 'ByteString' containing UTF-8 encoded text.
88decodeUtf8With :: OnDecodeError -> ByteString -> Text
91decodeUtf8With :: OnDecodeError -> ByteString -> Text
89−decodeUtf8With onErr bs = textP (fst a) 0 (snd a)
92+decodeUtf8With onErr (PS fp off len) = textP (fst a) 0 (snd a)
90 where
93 where
91− a = A.run2 (A.new len >>= outer 0 0)
94+ a = A.run2 (A.new len >>= unsafeIOToST . go)
92− len = B.length bs
93− idx = B.unsafeIndex bs
94 desc = "Data.Text.Encoding.decodeUtf8: Invalid UTF-8 stream"
95 desc = "Data.Text.Encoding.decodeUtf8: Invalid UTF-8 stream"
95− outer n0 m0 arr = go n0 m0
96+ go dest = withForeignPtr fp $ \ptr ->
96− where
97+ with (0::CSize) $ \destOffPtr -> do
97− go !n !m =
98+ let end = ptr `plusPtr` (off + len)
98− if m < len
99+ loop curPtr = do
99− then let !x1 = idx m
100+ curPtr' <- c_decode_utf8 (A.maBA dest) destOffPtr curPtr end
100− !m1 = m + 1
101+ if curPtr' == end
102+ then do
103+ n <- peek destOffPtr
104+ return (dest,fromIntegral n)
105+ else do
106+ x <- peek curPtr'
101− barf = case onErr desc (Just x1) of
107+ case onErr desc (Just x) of
102− Nothing -> go n m1
108+ Nothing -> loop $ curPtr' `plusPtr` 1
103− Just c -> do
109+ Just c -> do
104− w <- unsafeWrite arr n c
110+ destOff <- peek destOffPtr
105− go (n+w) m1
111+ w <- unsafeSTToIO $
106− in if U8.validate1 x1 then do
107− A.unsafeWrite arr n (fromIntegral x1)
112+ unsafeWrite dest (fromIntegral destOff) c
108− go (n+1) m1
113+ poke destOffPtr (destOff + fromIntegral w)
109− else if m1 < len then
114+ loop $ curPtr' `plusPtr` 1
110− let !x2 = idx m1; !m2 = m + 2 in
115+ loop (ptr `plusPtr` off)
111− if U8.validate2 x1 x2 then do
112− w <- unsafeWrite arr n (U8.chr2 x1 x2)
113− go (n+w) m2
114− else if m2 < len then
115− let !x3 = idx m2; !m3 = m + 3 in
116− if U8.validate3 x1 x2 x3 then do
117− w <- unsafeWrite arr n (U8.chr3 x1 x2 x3)
118− go (n+w) m3
119− else if m3 < len then
120− let !x4 = idx m3 in
121− if U8.validate4 x1 x2 x3 x4 then do
122− w <- unsafeWrite arr n (U8.chr4 x1 x2 x3 x4)
123− go (n+w) (m+4)
124− else barf
125− else barf
126− else barf
127− else barf
128− else return (arr,n)
129−{-# INLINE[0] decodeUtf8With #-}
116+{- INLINE[0] decodeUtf8With #-}
130
117
131-- | Decode a 'ByteString' containing UTF-8 encoded text that is known
118-- | Decode a 'ByteString' containing UTF-8 encoded text that is known
132-- to be valid.
119-- to be valid.
@@ -268,3 +255,7 @@ encodeUtf32LE txt = E.unstream (E.restreamUtf32LE (F.stream txt))
268encodeUtf32BE :: Text -> ByteString
255encodeUtf32BE :: Text -> ByteString
269encodeUtf32BE txt = E.unstream (E.restreamUtf32BE (F.stream txt))
256encodeUtf32BE txt = E.unstream (E.restreamUtf32BE (F.stream txt))
270{-# INLINE encodeUtf32BE #-}
257{-# INLINE encodeUtf32BE #-}
258+
259+foreign import ccall unsafe "_hs_text_decode_utf8" c_decode_utf8
260+ :: MutableByteArray# s -> Ptr CSize
261+ -> Ptr Word8 -> Ptr Word8 -> IO (Ptr Word8)
cbits/cbits.c+120
@@ -1,4 +1,14 @@
1+/*
2+ * Copyright (c) 2011 Bryan O'Sullivan <bos@serpentine.com>.
3+ *
4+ * Portions copyright (c) 2008-2010 Björn Höhrmann <bjoern@hoehrmann.de>.
5+ *
6+ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
7+ */
8+
1#include <string.h>
9#include <string.h>
10+#include <stdint.h>
11+#include <stdio.h>
2
12
3void _hs_text_memcpy(void *dest, size_t doff, const void *src, size_t soff,
13void _hs_text_memcpy(void *dest, size_t doff, const void *src, size_t soff,
4 size_t n)
14 size_t n)
@@ -11,3 +21,113 @@ int _hs_text_memcmp(const void *a, size_t aoff, const void *b, size_t boff,
11{
21{
12 return memcmp(a + (aoff<<1), b + (boff<<1), n<<1);
22 return memcmp(a + (aoff<<1), b + (boff<<1), n<<1);
13}
23}
24+
25+#define UTF8_ACCEPT 0
26+#define UTF8_REJECT 12
27+
28+static const uint8_t utf8d[] = {
29+ /*
30+ * The first part of the table maps bytes to character classes that
31+ * to reduce the size of the transition table and create bitmasks.
32+ */
33+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
34+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
35+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
36+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
37+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
38+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
39+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
40+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
41+
42+ /*
43+ * The second part is a transition table that maps a combination of
44+ * a state of the automaton and a character class to a state.
45+ */
46+ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
47+ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
48+ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
49+ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
50+ 12,36,12,12,12,12,12,12,12,12,12,12,
51+};
52+
53+static inline uint32_t
54+decode(uint32_t *state, uint32_t* codep, uint32_t byte) {
55+ uint32_t type = utf8d[byte];
56+
57+ *codep = (*state != UTF8_ACCEPT) ?
58+ (byte & 0x3fu) | (*codep << 6) :
59+ (0xff >> type) & (byte);
60+
61+ return *state = utf8d[256 + *state + type];
62+}
63+
64+/*
65+ * A best-effort decoder. Runs until it hits either end of input or
66+ * the start of an invalid byte sequence.
67+ *
68+ * At exit, updates *destoff with the next offset to write to, and
69+ * returns the next source offset to read from.
70+ */
71+uint8_t const *
72+_hs_text_decode_utf8(uint16_t *dest, size_t *destoff,
73+ const uint8_t const *src, const uint8_t const *srcend)
74+{
75+ uint16_t *d = dest + *destoff;
76+ const uint8_t const *s = src;
77+ uint32_t state = UTF8_ACCEPT;
78+
79+ while (s < srcend) {
80+ uint32_t codepoint;
81+
82+#if defined(__i386__) || defined(__x86_64__)
83+ /*
84+ * This code will only work on a little-endian system that
85+ * supports unaligned loads.
86+ *
87+ * It gives a substantial speed win on data that is purely or
88+ * partly ASCII (e.g. HTML), at only a slight cost on purely
89+ * non-ASCII text.
90+ */
91+
92+ if (state == UTF8_ACCEPT) {
93+ while (s < srcend - 4) {
94+ codepoint = *((uint32_t *) s);
95+ if ((codepoint & 0x80808080) != 0)
96+ break;
97+ s += 4;
98+
99+ /*
100+ * Tried 32-bit stores here, but the extra bit-twiddling
101+ * slowed the code down.
102+ */
103+
104+ *d++ = (uint16_t) (codepoint & 0xff);
105+ *d++ = (uint16_t) ((codepoint >> 8) & 0xff);
106+ *d++ = (uint16_t) ((codepoint >> 16) & 0xff);
107+ *d++ = (uint16_t) ((codepoint >> 24) & 0xff);
108+ }
109+ }
110+#endif
111+
112+ if (decode(&state, &codepoint, *s++) != UTF8_ACCEPT) {
113+ if (state != UTF8_REJECT)
114+ continue;
115+ break;
116+ }
117+
118+ if (codepoint <= 0xffff)
119+ *d++ = (uint16_t) codepoint;
120+ else {
121+ *d++ = (uint16_t) (0xD7C0 + (codepoint >> 10));
122+ *d++ = (uint16_t) (0xDC00 + (codepoint & 0x3FF));
123+ }
124+ }
125+
126+ /* Error recovery - if we're not in a valid finishing state, back up. */
127+ if (state != UTF8_ACCEPT)
128+ s -= 1;
129+
130+ *destoff = d - dest;
131+
132+ return s;
133+}