text-rope-0.1: bench/Main.hs
-- |
-- Copyright: (c) 2021-2022 Andrew Lelechenko
-- Licence: BSD3
-- Maintainer: Andrew Lelechenko <andrew.lelechenko@gmail.com>
{-# LANGUAGE CPP #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Main
( main
) where
import Prelude (fromIntegral, (<$>), (*))
import Paths_text_rope (getDataFileName)
import Data.Function (($), (.))
import Data.Int (Int)
import Data.List (foldl', take, zip, length)
import Data.Maybe (fromJust)
import Data.Monoid (Monoid, mappend)
import Data.Proxy (Proxy(..))
import Data.Semigroup ((<>))
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Word (Word)
import System.IO (IO)
import System.IO.Unsafe (unsafePerformIO)
import System.Random (randomRs, mkStdGen)
import Test.Tasty.Bench (defaultMain, bgroup, bench, nf, bcompare)
import qualified Data.Text.Rope as CharRope
import qualified Data.Text.Utf16.Rope as Utf16Rope
#ifdef MIN_VERSION_core_text
import qualified Core.Text.Rope as CoreText
#endif
#ifdef MIN_VERSION_rope_utf16_splay
import qualified Data.Rope.UTF16 as RopeSplay
#endif
#ifdef MIN_VERSION_yi_rope
import qualified Yi.Rope as YiRope
#endif
main :: IO ()
main = defaultMain
[ bgroup "Split at position"
[ bgroup "Unicode"
[ bench "text-rope" $ nf (editByPosition (Proxy @CharRope.Rope)) txt
#ifdef MIN_VERSION_yi_rope
, bcompare "$NF == \"text-rope\" && $(NF-1) == \"Unicode\" && $(NF-2) == \"Split at position\""
$ bench "yi-rope" $ nf (editByPosition (Proxy @YiRope.YiString)) txt
#endif
]
, bgroup "UTF-16"
[ bench "text-rope" $ nf (editByPosition (Proxy @Utf16Rope.Rope)) txt
#ifdef MIN_VERSION_rope_utf16_splay
, bcompare "$NF == \"text-rope\" && $(NF-1) == \"UTF-16\" && $(NF-2) == \"Split at position\""
$ bench "rope-utf16-splay" $ nf (editByPosition (Proxy @RopeSplay.Rope)) txt
#endif
]
]
, bgroup "Split at offset"
[ bgroup "Unicode"
[ bench "text-rope" $ nf (editByOffset (Proxy @CharRope.Rope)) txt
#ifdef MIN_VERSION_core_text
, bcompare "$NF == \"text-rope\" && $(NF-1) == \"Unicode\" && $(NF-2) == \"Split at offset\""
$ bench "core-text" $ nf (editByOffset (Proxy @CoreText.Rope)) txt
#endif
#ifdef MIN_VERSION_yi_rope
, bcompare "$NF == \"text-rope\" && $(NF-1) == \"Unicode\" && $(NF-2) == \"Split at offset\""
$ bench "yi-rope" $ nf (editByOffset (Proxy @YiRope.YiString)) txt
#endif
]
, bgroup "UTF-16"
[ bench "text-rope" $ nf (editByOffset (Proxy @Utf16Rope.Rope)) txt
#ifdef MIN_VERSION_rope_utf16_splay
, bcompare "$NF == \"text-rope\" && $(NF-1) == \"UTF-16\" && $(NF-2) == \"Split at offset\""
$ bench "rope-utf16-splay" $ nf (editByOffset (Proxy @RopeSplay.Rope)) txt
#endif
]
]
]
scale :: Int
scale = 1
txt :: T.Text
txt = unsafePerformIO $ do
fn <- getDataFileName "bench/bench.txt"
T.replicate scale <$> T.readFile fn
{-# NOINLINE txt #-}
randomOffsets :: [Word]
randomOffsets = take (1000 * scale) $
randomRs (0, fromIntegral $ T.length txt) (mkStdGen 33)
{-# NOINLINE randomOffsets #-}
randomPositions :: [(Word, Word)]
randomPositions = take (1000 * scale) $ zip ls cs
where
ls = randomRs (0, fromIntegral $ length $ T.lines txt) (mkStdGen 42)
-- assuming reasonable line length is < 80
cs = randomRs (0, 80) (mkStdGen 24)
{-# NOINLINE randomPositions #-}
class Monoid a => Splittable a where
fromText :: T.Text -> a
toText :: a -> T.Text
splitAt :: Word -> a -> (a, a)
class Splittable a => SplittableAtPosition a where
splitAtPosition :: Word -> Word -> a -> (a, a)
instance Splittable CharRope.Rope where
fromText = CharRope.fromText
toText = CharRope.toText
splitAt = CharRope.splitAt
instance SplittableAtPosition CharRope.Rope where
splitAtPosition l c = CharRope.splitAtPosition (CharRope.Position l c)
instance Splittable Utf16Rope.Rope where
fromText = Utf16Rope.fromText
toText = Utf16Rope.toText
splitAt = (fromJust . ) . Utf16Rope.splitAt
instance SplittableAtPosition Utf16Rope.Rope where
splitAtPosition l c = fromJust . Utf16Rope.splitAtPosition (Utf16Rope.Position l c)
#ifdef MIN_VERSION_core_text
instance Splittable CoreText.Rope where
fromText = CoreText.intoRope
toText = CoreText.fromRope
splitAt = CoreText.splitRope . fromIntegral
#endif
#ifdef MIN_VERSION_yi_rope
instance Splittable YiRope.YiString where
fromText = YiRope.fromText
toText = YiRope.toText
splitAt = YiRope.splitAt . fromIntegral
instance SplittableAtPosition YiRope.YiString where
splitAtPosition l c orig = (before `mappend` mid, after)
where
(before, after') = YiRope.splitAtLine (fromIntegral l) orig
(mid, after) = YiRope.splitAt (fromIntegral c) after'
#endif
#ifdef MIN_VERSION_rope_utf16_splay
instance Splittable RopeSplay.Rope where
fromText = RopeSplay.fromText
toText = RopeSplay.toText
splitAt = RopeSplay.splitAt . fromIntegral
instance SplittableAtPosition RopeSplay.Rope where
splitAtPosition l c orig = RopeSplay.splitAt k orig
where
k = RopeSplay.rowColumnCodeUnits (RopeSplay.RowColumn (fromIntegral l) (fromIntegral c)) orig
#endif
editByOffset :: forall a. Splittable a => Proxy a -> T.Text -> T.Text
editByOffset _ txt = (toText @a) $ foldl' edit (fromText txt) randomOffsets
where
edit orig c = before `mappend` mid `mappend` after
where
(before, after') = splitAt c orig
-- edit 10 characters
(mid, after) = splitAt 10 after'
editByPosition :: forall a. SplittableAtPosition a => Proxy a -> T.Text -> T.Text
editByPosition _ txt = (toText @a) $ foldl' edit (fromText txt) randomPositions
where
edit orig (l, c) = before `mappend` mid `mappend` after
where
(before, after') = splitAtPosition l c orig
-- edit 10 characters
(mid, after) = splitAt 10 after'