@@ -0,0 +1,470 @@
1+-- | Benchmarks various pure functions from the Text library
3+{-# LANGUAGE BangPatterns, GADTs, MagicHash #-}
4+{-# OPTIONS_GHC -fno-warn-orphans #-}
5+module Data.Text.Benchmarks.Pure
9+import Control.DeepSeq (NFData (..))
10+import Control.Exception (evaluate)
11+import Criterion (Benchmark, bgroup, bench, nf)
12+import Data.Char (toLower, toUpper)
13+import Data.Monoid (mappend, mempty)
14+import GHC.Base (Char (..), Int (..), chr#, ord#, (+#))
15+import qualified Data.ByteString.Char8 as BS
16+import qualified Data.ByteString.Lazy.Char8 as BL
17+import qualified Data.ByteString.Lazy.Internal as BL
18+import qualified Data.ByteString.UTF8 as UTF8
19+import qualified Data.List as L
20+import qualified Data.Text as T
21+import qualified Data.Text.Encoding as T
22+import qualified Data.Text.Lazy as TL
23+import qualified Data.Text.Lazy.Builder as TB
24+import qualified Data.Text.Lazy.Encoding as TL
26+benchmark :: FilePath -> IO Benchmark
28+ -- Evaluate stuff before actually running the benchmark, we don't want to
32+ bsa <- BS.readFile fp
34+ -- Text A/B, LazyText A/B
35+ ta <- evaluate $ T.decodeUtf8 bsa
36+ tb <- evaluate $ T.toUpper ta
37+ tla <- evaluate $ TL.fromChunks (T.chunksOf 16376 ta)
38+ tlb <- evaluate $ TL.fromChunks (T.chunksOf 16376 tb)
40+ -- ByteString B, LazyByteString A/B
41+ bsb <- evaluate $ T.encodeUtf8 tb
42+ bla <- evaluate $ BL.fromChunks (chunksOf 16376 bsa)
43+ blb <- evaluate $ BL.fromChunks (chunksOf 16376 bsb)
46+ sa <- evaluate $ UTF8.toString bsa
47+ sb <- evaluate $ T.unpack tb
50+ bsa_len <- evaluate $ BS.length bsa
51+ ta_len <- evaluate $ T.length ta
52+ bla_len <- evaluate $ BL.length bla
53+ tla_len <- evaluate $ TL.length tla
54+ sa_len <- evaluate $ L.length sa
57+ bsl <- evaluate $ BS.lines bsa
58+ bll <- evaluate $ BL.lines bla
59+ tl <- evaluate $ T.lines ta
60+ tll <- evaluate $ TL.lines tla
61+ sl <- evaluate $ L.lines sa
63+ return $ bgroup "Pure"
65+ [ benchT $ nf (T.append tb) ta
66+ , benchTL $ nf (TL.append tlb) tla
67+ , benchBS $ nf (BS.append bsb) bsa
68+ , benchBSL $ nf (BL.append blb) bla
69+ , benchS $ nf ((++) sb) sa
72+ [ benchT $ nf T.concat tl
73+ , benchTL $ nf TL.concat tll
74+ , benchBS $ nf BS.concat bsl
75+ , benchBSL $ nf BL.concat bll
76+ , benchS $ nf L.concat sl
79+ [ benchT $ nf (T.cons c) ta
80+ , benchTL $ nf (TL.cons c) tla
81+ , benchBS $ nf (BS.cons c) bsa
82+ , benchBSL $ nf (BL.cons c) bla
83+ , benchS $ nf (c:) sa
86+ [ benchT $ nf (T.concatMap (T.replicate 3 . T.singleton)) ta
87+ , benchTL $ nf (TL.concatMap (TL.replicate 3 . TL.singleton)) tla
88+ , benchBS $ nf (BS.concatMap (BS.replicate 3)) bsa
89+ , benchBSL $ nf (BL.concatMap (BL.replicate 3)) bla
90+ , benchS $ nf (L.concatMap (L.replicate 3 . (:[]))) sa
93+ [ benchT $ nf T.decodeUtf8 bsa
94+ , benchTL $ nf TL.decodeUtf8 bla
95+ , benchBS $ nf BS.unpack bsa
96+ , benchBSL $ nf BL.unpack bla
97+ , benchS $ nf UTF8.toString bsa
100+ [ benchT $ nf (T.drop (ta_len `div` 3)) ta
101+ , benchTL $ nf (TL.drop (tla_len `div` 3)) tla
102+ , benchBS $ nf (BS.drop (bsa_len `div` 3)) bsa
103+ , benchBSL $ nf (BL.drop (bla_len `div` 3)) bla
104+ , benchS $ nf (L.drop (sa_len `div` 3)) sa
107+ [ benchT $ nf T.encodeUtf8 ta
108+ , benchTL $ nf TL.encodeUtf8 tla
109+ , benchBS $ nf BS.pack sa
110+ , benchBSL $ nf BL.pack sa
111+ , benchS $ nf UTF8.fromString sa
114+ [ benchT $ nf (T.filter p0) ta
115+ , benchTL $ nf (TL.filter p0) tla
116+ , benchBS $ nf (BS.filter p0) bsa
117+ , benchBSL $ nf (BL.filter p0) bla
118+ , benchS $ nf (L.filter p0) sa
120+ , bgroup "filter.filter"
121+ [ benchT $ nf (T.filter p1 . T.filter p0) ta
122+ , benchTL $ nf (TL.filter p1 . TL.filter p0) tla
123+ , benchBS $ nf (BS.filter p1 . BS.filter p0) bsa
124+ , benchBSL $ nf (BL.filter p1 . BL.filter p0) bla
125+ , benchS $ nf (L.filter p1 . L.filter p0) sa
128+ [ benchT $ nf (T.foldl' len 0) ta
129+ , benchTL $ nf (TL.foldl' len 0) tla
130+ , benchBS $ nf (BS.foldl' len 0) bsa
131+ , benchBSL $ nf (BL.foldl' len 0) bla
132+ , benchS $ nf (L.foldl' len 0) sa
135+ [ benchT $ nf (L.length . T.foldr (:) []) ta
136+ , benchTL $ nf (L.length . TL.foldr (:) []) tla
137+ , benchBS $ nf (L.length . BS.foldr (:) []) bsa
138+ , benchBSL $ nf (L.length . BL.foldr (:) []) bla
139+ , benchS $ nf (L.length . L.foldr (:) []) sa
142+ [ benchT $ nf T.head ta
143+ , benchTL $ nf TL.head tla
144+ , benchBS $ nf BS.head bsa
145+ , benchBSL $ nf BL.head bla
146+ , benchS $ nf L.head sa
149+ [ benchT $ nf T.init ta
150+ , benchTL $ nf TL.init tla
151+ , benchBS $ nf BS.init bsa
152+ , benchBSL $ nf BL.init bla
153+ , benchS $ nf L.init sa
155+ , bgroup "intercalate"
156+ [ benchT $ nf (T.intercalate tsw) tl
157+ , benchTL $ nf (TL.intercalate tlw) tll
158+ , benchBS $ nf (BS.intercalate bsw) bsl
159+ , benchBSL $ nf (BL.intercalate blw) bll
160+ , benchS $ nf (L.intercalate lw) sl
162+ , bgroup "intersperse"
163+ [ benchT $ nf (T.intersperse c) ta
164+ , benchTL $ nf (TL.intersperse c) tla
165+ , benchBS $ nf (BS.intersperse c) bsa
166+ , benchBSL $ nf (BL.intersperse c) bla
167+ , benchS $ nf (L.intersperse c) sa
169+ , bgroup "isInfixOf"
170+ [ benchT $ nf (T.isInfixOf tsw) ta
171+ , benchTL $ nf (TL.isInfixOf tlw) tla
172+ , benchBS $ nf (BS.isInfixOf bsw) bsa
173+ -- no isInfixOf for lazy bytestrings
174+ , benchS $ nf (L.isInfixOf lw) sa
177+ [ benchT $ nf T.last ta
178+ , benchTL $ nf TL.last tla
179+ , benchBS $ nf BS.last bsa
180+ , benchBSL $ nf BL.last bla
181+ , benchS $ nf L.last sa
184+ [ benchT $ nf (T.map f) ta
185+ , benchTL $ nf (TL.map f) tla
186+ , benchBS $ nf (BS.map f) bsa
187+ , benchBSL $ nf (BL.map f) bla
188+ , benchS $ nf (L.map f) sa
190+ , bgroup "mapAccumL"
191+ [ benchT $ nf (T.mapAccumL g 0) ta
192+ , benchTL $ nf (TL.mapAccumL g 0) tla
193+ , benchBS $ nf (BS.mapAccumL g 0) bsa
194+ , benchBSL $ nf (BL.mapAccumL g 0) bla
195+ , benchS $ nf (L.mapAccumL g 0) sa
197+ , bgroup "mapAccumR"
198+ [ benchT $ nf (T.mapAccumR g 0) ta
199+ , benchTL $ nf (TL.mapAccumR g 0) tla
200+ , benchBS $ nf (BS.mapAccumR g 0) bsa
201+ , benchBSL $ nf (BL.mapAccumR g 0) bla
202+ , benchS $ nf (L.mapAccumR g 0) sa
205+ [ benchT $ nf (T.map f . T.map f) ta
206+ , benchTL $ nf (TL.map f . TL.map f) tla
207+ , benchBS $ nf (BS.map f . BS.map f) bsa
208+ , benchBSL $ nf (BL.map f . BL.map f) bla
209+ , benchS $ nf (L.map f . L.map f) sa
211+ , bgroup "replicate char"
212+ [ benchT $ nf (T.replicate bsa_len) (T.singleton c)
213+ , benchTL $ nf (TL.replicate (fromIntegral bsa_len)) (TL.singleton c)
214+ , benchBS $ nf (BS.replicate bsa_len) c
215+ , benchBSL $ nf (BL.replicate (fromIntegral bsa_len)) c
216+ , benchS $ nf (L.replicate bsa_len) c
218+ , bgroup "replicate string"
219+ [ benchT $ nf (T.replicate (bsa_len `div` T.length tsw)) tsw
220+ , benchTL $ nf (TL.replicate (fromIntegral bsa_len `div` TL.length tlw)) tlw
221+ , benchS $ nf (replicat (bsa_len `div` T.length tsw)) lw
224+ [ benchT $ nf T.reverse ta
225+ , benchTL $ nf TL.reverse tla
226+ , benchBS $ nf BS.reverse bsa
227+ , benchBSL $ nf BL.reverse bla
228+ , benchS $ nf L.reverse sa
231+ [ benchT $ nf (T.take (ta_len `div` 3)) ta
232+ , benchTL $ nf (TL.take (tla_len `div` 3)) tla
233+ , benchBS $ nf (BS.take (bsa_len `div` 3)) bsa
234+ , benchBSL $ nf (BL.take (bla_len `div` 3)) bla
235+ , benchS $ nf (L.take (sa_len `div` 3)) sa
238+ [ benchT $ nf T.tail ta
239+ , benchTL $ nf TL.tail tla
240+ , benchBS $ nf BS.tail bsa
241+ , benchBSL $ nf BL.tail bla
242+ , benchS $ nf L.tail sa
245+ [ benchT $ nf T.toLower ta
246+ , benchTL $ nf TL.toLower tla
247+ , benchBS $ nf (BS.map toLower) bsa
248+ , benchBSL $ nf (BL.map toLower) bla
249+ , benchS $ nf (L.map toLower) sa
252+ [ benchT $ nf T.toUpper ta
253+ , benchTL $ nf TL.toUpper tla
254+ , benchBS $ nf (BS.map toUpper) bsa
255+ , benchBSL $ nf (BL.map toUpper) bla
256+ , benchS $ nf (L.map toUpper) sa
259+ [ benchT $ nf T.words ta
260+ , benchTL $ nf TL.words tla
261+ , benchBS $ nf BS.words bsa
262+ , benchBSL $ nf BL.words bla
263+ , benchS $ nf L.words sa
266+ [ benchT $ nf (T.zipWith min tb) ta
267+ , benchTL $ nf (TL.zipWith min tlb) tla
268+ , benchBS $ nf (BS.zipWith min bsb) bsa
269+ , benchBSL $ nf (BL.zipWith min blb) bla
270+ , benchS $ nf (L.zipWith min sb) sa
274+ [ benchT $ nf (T.length . T.cons c) ta
275+ , benchTL $ nf (TL.length . TL.cons c) tla
276+ , benchBS $ nf (BS.length . BS.cons c) bsa
277+ , benchBSL $ nf (BL.length . BL.cons c) bla
278+ , benchS $ nf (L.length . (:) c) sa
281+ [ benchT $ nf (T.length . T.decodeUtf8) bsa
282+ , benchTL $ nf (TL.length . TL.decodeUtf8) bla
283+ , benchBS $ nf (L.length . BS.unpack) bsa
284+ , benchBSL $ nf (L.length . BL.unpack) bla
285+ , bench "StringUTF8" $ nf (L.length . UTF8.toString) bsa
288+ [ benchT $ nf (T.length . T.drop (ta_len `div` 3)) ta
289+ , benchTL $ nf (TL.length . TL.drop (tla_len `div` 3)) tla
290+ , benchBS $ nf (BS.length . BS.drop (bsa_len `div` 3)) bsa
291+ , benchBSL $ nf (BL.length . BL.drop (bla_len `div` 3)) bla
292+ , benchS $ nf (L.length . L.drop (sa_len `div` 3)) sa
295+ [ benchT $ nf (T.length . T.filter p0) ta
296+ , benchTL $ nf (TL.length . TL.filter p0) tla
297+ , benchBS $ nf (BS.length . BS.filter p0) bsa
298+ , benchBSL $ nf (BL.length . BL.filter p0) bla
299+ , benchS $ nf (L.length . L.filter p0) sa
301+ , bgroup "filter.filter"
302+ [ benchT $ nf (T.length . T.filter p1 . T.filter p0) ta
303+ , benchTL $ nf (TL.length . TL.filter p1 . TL.filter p0) tla
304+ , benchBS $ nf (BS.length . BS.filter p1 . BS.filter p0) bsa
305+ , benchBSL $ nf (BL.length . BL.filter p1 . BL.filter p0) bla
306+ , benchS $ nf (L.length . L.filter p1 . L.filter p0) sa
309+ [ benchT $ nf (T.length . T.init) ta
310+ , benchTL $ nf (TL.length . TL.init) tla
311+ , benchBS $ nf (BS.length . BS.init) bsa
312+ , benchBSL $ nf (BL.length . BL.init) bla
313+ , benchS $ nf (L.length . L.init) sa
315+ , bgroup "intercalate"
316+ [ benchT $ nf (T.length . T.intercalate tsw) tl
317+ , benchTL $ nf (TL.length . TL.intercalate tlw) tll
318+ , benchBS $ nf (BS.length . BS.intercalate bsw) bsl
319+ , benchBSL $ nf (BL.length . BL.intercalate blw) bll
320+ , benchS $ nf (L.length . L.intercalate lw) sl
322+ , bgroup "intersperse"
323+ [ benchT $ nf (T.length . T.intersperse c) ta
324+ , benchTL $ nf (TL.length . TL.intersperse c) tla
325+ , benchBS $ nf (BS.length . BS.intersperse c) bsa
326+ , benchBSL $ nf (BL.length . BL.intersperse c) bla
327+ , benchS $ nf (L.length . L.intersperse c) sa
330+ [ benchT $ nf (T.length . T.map f) ta
331+ , benchTL $ nf (TL.length . TL.map f) tla
332+ , benchBS $ nf (BS.length . BS.map f) bsa
333+ , benchBSL $ nf (BL.length . BL.map f) bla
334+ , benchS $ nf (L.length . L.map f) sa
337+ [ benchT $ nf (T.length . T.map f . T.map f) ta
338+ , benchTL $ nf (TL.length . TL.map f . TL.map f) tla
339+ , benchBS $ nf (BS.length . BS.map f . BS.map f) bsa
340+ , benchS $ nf (L.length . L.map f . L.map f) sa
342+ , bgroup "replicate char"
343+ [ benchT $ nf (T.length . T.replicate bsa_len) (T.singleton c)
344+ , benchTL $ nf (TL.length . TL.replicate (fromIntegral bsa_len)) (TL.singleton c)
345+ , benchBS $ nf (BS.length . BS.replicate bsa_len) c
346+ , benchBSL $ nf (BL.length . BL.replicate (fromIntegral bsa_len)) c
347+ , benchS $ nf (L.length . L.replicate bsa_len) c
349+ , bgroup "replicate string"
350+ [ benchT $ nf (T.length . T.replicate (bsa_len `div` T.length tsw)) tsw
351+ , benchTL $ nf (TL.length . TL.replicate (fromIntegral bsa_len `div` TL.length tlw)) tlw
352+ , benchS $ nf (L.length . replicat (bsa_len `div` T.length tsw)) lw
355+ [ benchT $ nf (T.length . T.take (ta_len `div` 3)) ta
356+ , benchTL $ nf (TL.length . TL.take (tla_len `div` 3)) tla
357+ , benchBS $ nf (BS.length . BS.take (bsa_len `div` 3)) bsa
358+ , benchBSL $ nf (BL.length . BL.take (bla_len `div` 3)) bla
359+ , benchS $ nf (L.length . L.take (sa_len `div` 3)) sa
362+ [ benchT $ nf (T.length . T.tail) ta
363+ , benchTL $ nf (TL.length . TL.tail) tla
364+ , benchBS $ nf (BS.length . BS.tail) bsa
365+ , benchBSL $ nf (BL.length . BL.tail) bla
366+ , benchS $ nf (L.length . L.tail) sa
369+ [ benchT $ nf (T.length . T.toLower) ta
370+ , benchTL $ nf (TL.length . TL.toLower) tla
371+ , benchBS $ nf (BS.length . BS.map toLower) bsa
372+ , benchBSL $ nf (BL.length . BL.map toLower) bla
373+ , benchS $ nf (L.length . L.map toLower) sa
376+ [ benchT $ nf (T.length . T.toUpper) ta
377+ , benchTL $ nf (TL.length . TL.toUpper) tla
378+ , benchBS $ nf (BS.length . BS.map toUpper) bsa
379+ , benchBSL $ nf (BL.length . BL.map toUpper) bla
380+ , benchS $ nf (L.length . L.map toUpper) sa
383+ [ benchT $ nf (L.length . T.words) ta
384+ , benchTL $ nf (L.length . TL.words) tla
385+ , benchBS $ nf (L.length . BS.words) bsa
386+ , benchBSL $ nf (L.length . BL.words) bla
387+ , benchS $ nf (L.length . L.words) sa
390+ [ benchT $ nf (T.length . T.zipWith min tb) ta
391+ , benchTL $ nf (TL.length . TL.zipWith min tlb) tla
392+ , benchBS $ nf (L.length . BS.zipWith min bsb) bsa
393+ , benchBSL $ nf (L.length . BL.zipWith min blb) bla
394+ , benchS $ nf (L.length . L.zipWith min sb) sa
398+ [ bench "mappend char" $ nf (TL.length . TB.toLazyText . mappendNChar 'a') 10000
399+ , bench "mappend 8 char" $ nf (TL.length . TB.toLazyText . mappend8Char) 'a'
400+ , bench "mappend text" $ nf (TL.length . TB.toLazyText . mappendNText short) 10000
404+ benchS = bench "String"
405+ benchT = bench "Text"
406+ benchTL = bench "LazyText"
407+ benchBS = bench "ByteString"
408+ benchBSL = bench "LazyByteString"
414+ bsw = UTF8.fromString lw
415+ blw = BL.fromChunks [bsw]
417+ tlw = TL.fromChunks [tsw]
418+ f (C# c#) = C# (chr# (ord# c# +# 1#))
419+ g (I# i#) (C# c#) = (I# (i# +# 1#), C# (chr# (ord# c# +# i#)))
420+ len l _ = l + (1::Int)
421+ replicat n = concat . L.replicate n
422+ short = T.pack "short"
424+instance NFData BS.ByteString
426+instance NFData BL.ByteString where
428+ rnf (BL.Chunk _ ts) = rnf ts
431+ B :: NFData a => a -> B
433+instance NFData B where
436+-- | Split a bytestring in chunks
438+chunksOf :: Int -> BS.ByteString -> [BS.ByteString]
441+ go t = case BS.splitAt k t of
442+ (a,b) | BS.null a -> []
443+ | otherwise -> a : go b
445+-- | Append a character n times
447+mappendNChar :: Char -> Int -> TB.Builder
448+mappendNChar c n = go 0
451+ | i < n = TB.singleton c `mappend` go (i+1)
452+ | otherwise = mempty
454+-- | Gives more opportunity for inlining and elimination of unnecesary
457+mappend8Char :: Char -> TB.Builder
458+mappend8Char c = TB.singleton c `mappend` TB.singleton c `mappend`
459+ TB.singleton c `mappend` TB.singleton c `mappend`
460+ TB.singleton c `mappend` TB.singleton c `mappend`
461+ TB.singleton c `mappend` TB.singleton c
463+-- | Append a text N times
465+mappendNText :: T.Text -> Int -> TB.Builder
466+mappendNText t n = go 0
469+ | i < n = TB.fromText t `mappend` go (i+1)
470+ | otherwise = mempty