on master help sign in
Portable native UTF-8 decoder gives 3.7x faster decoding

This code is derived from Björn Höhrmann's UTF-8 decoder. Compared to the original Haskell decoder from cac7dbcbc392, it's between 2.17 and 3.68 times faster. It's even between 1.18 and 3.58 times faster than the improved Haskell decoder from 71ead801296a. The x86-specific decoding path gives a substantial win for entirely and partly ASCII text, e.g. HTML and XML, at the cost of being about 17% slower than the portable C decoder for entirely non-ASCII text.

commitefa87327e5bdb43d5cdc09b5633f7d17b0b0b5d4 authorBryan O'Sullivan <bos@serpentine.com> dateSun 10 Jul 2011, 14:03 (15 years ago) parent6285fb6
2 files changed+155−44
Data/Text/Encoding.hs+35−44 cbits/cbits.c+120
Data/Text/Encoding.hs+35−44
@@ -1,4 +1,5 @@
1−{-# LANGUAGE BangPatterns #-}
1+{-# LANGUAGE BangPatterns, ForeignFunctionInterface, MagicHash,
2+ UnliftedFFITypes #-}
23-- |
34-- Module : Data.Text.Encoding
45-- Copyright : (c) 2008, 2009 Tom Harper,
@@ -47,23 +48,25 @@ module Data.Text.Encoding
4748 ) where
4849
4950import Control.Exception (evaluate, try)
51+import Control.Monad.ST (unsafeIOToST, unsafeSTToIO)
5052import Data.Bits ((.&.))
5153import Data.ByteString as B
5254import Data.ByteString.Internal as B
53−import Data.ByteString.Unsafe as B
5455import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
5556import Data.Text.Internal (Text(..), textP)
5657import Data.Text.UnsafeChar (ord, unsafeWrite)
5758import Data.Text.UnsafeShift (shiftL, shiftR)
5859import Data.Word (Word8)
60+import Foreign.C.Types (CSize)
5961import Foreign.ForeignPtr (withForeignPtr)
60−import Foreign.Ptr (plusPtr)
61−import Foreign.Storable (poke)
62+import Foreign.Marshal.Utils (with)
63+import Foreign.Ptr (Ptr, plusPtr)
64+import Foreign.Storable (peek, poke)
65+import GHC.Base (MutableByteArray#)
6266import System.IO.Unsafe (unsafePerformIO)
6367import qualified Data.Text.Array as A
6468import qualified Data.Text.Encoding.Fusion as E
6569import qualified Data.Text.Encoding.Utf16 as U16
66−import qualified Data.Text.Encoding.Utf8 as U8
6770import qualified Data.Text.Fusion as F
6871
6972-- $strict
@@ -86,47 +89,31 @@ decodeASCII bs = F.unstream (E.streamASCII bs)
8689
8790-- | Decode a 'ByteString' containing UTF-8 encoded text.
8891decodeUtf8With :: 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)
9093 where
91− a = A.run2 (A.new len >>= outer 0 0)
92− len = B.length bs
93− idx = B.unsafeIndex bs
94+ a = A.run2 (A.new len >>= unsafeIOToST . go)
9495 desc = "Data.Text.Encoding.decodeUtf8: Invalid UTF-8 stream"
95− outer n0 m0 arr = go n0 m0
96− where
97− go !n !m =
98− if m < len
99− then let !x1 = idx m
100− !m1 = m + 1
101− barf = case onErr desc (Just x1) of
102− Nothing -> go n m1
103− Just c -> do
104− w <- unsafeWrite arr n c
105− go (n+w) m1
106− in if U8.validate1 x1 then do
107− A.unsafeWrite arr n (fromIntegral x1)
108− go (n+1) m1
109− else if m1 < len then
110− let !x2 = idx m1; !m2 = m + 2 in
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 #-}
96+ go dest = withForeignPtr fp $ \ptr ->
97+ with (0::CSize) $ \destOffPtr -> do
98+ let end = ptr `plusPtr` (off + len)
99+ loop curPtr = do
100+ curPtr' <- c_decode_utf8 (A.maBA dest) destOffPtr curPtr end
101+ if curPtr' == end
102+ then do
103+ n <- peek destOffPtr
104+ return (dest,fromIntegral n)
105+ else do
106+ x <- peek curPtr'
107+ case onErr desc (Just x) of
108+ Nothing -> loop $ curPtr' `plusPtr` 1
109+ Just c -> do
110+ destOff <- peek destOffPtr
111+ w <- unsafeSTToIO $
112+ unsafeWrite dest (fromIntegral destOff) c
113+ poke destOffPtr (destOff + fromIntegral w)
114+ loop $ curPtr' `plusPtr` 1
115+ loop (ptr `plusPtr` off)
116+{- INLINE[0] decodeUtf8With #-}
130117
131118-- | Decode a 'ByteString' containing UTF-8 encoded text that is known
132119-- to be valid.
@@ -268,3 +255,7 @@ encodeUtf32LE txt = E.unstream (E.restreamUtf32LE (F.stream txt))
268255encodeUtf32BE :: Text -> ByteString
269256encodeUtf32BE txt = E.unstream (E.restreamUtf32BE (F.stream txt))
270257{-# 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+
19#include <string.h>
10+#include <stdint.h>
11+#include <stdio.h>
212
313void _hs_text_memcpy(void *dest, size_t doff, const void *src, size_t soff,
414 size_t n)
@@ -11,3 +21,113 @@ int _hs_text_memcmp(const void *a, size_t aoff, const void *b, size_t boff,
1121{
1222 return memcmp(a + (aoff<<1), b + (boff<<1), n<<1);
1323}
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+}