Skip to content

Commit

Permalink
Faster masking, also add a benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
ethercrow authored and Dmitry Ivanov committed Feb 1, 2017
1 parent 26ab750 commit 76a4e82
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 4 deletions.
64 changes: 64 additions & 0 deletions benchmarks/mask.hs
@@ -0,0 +1,64 @@
{-# language BangPatterns #-}
{-# language OverloadedStrings #-}

import Criterion
import Criterion.Main

import Network.WebSockets.Hybi13.Mask

import Data.Bits (shiftR, xor)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL

setupEnv = do
let kilo = BL.replicate 1024 37
mega = BL.replicate (1024 * 1024) 37
return (kilo, mega)

maskPayload' :: Mask -> BL.ByteString -> BL.ByteString
maskPayload' Nothing = id
maskPayload' (Just mask) = snd . BL.mapAccumL f (cycle $ B.unpack mask)
where
f [] !c = ([], c)
f (m:ms) !c = (ms, m `xor` c)

main = defaultMain [
env setupEnv $ \ ~(kilo, mega) -> bgroup "main"
[ bgroup "kilobyte payload"
[ bgroup "zero_mask"
[ bench "current" $ nf (maskPayload (Just "\x00\x00\x00\x00")) kilo
, bench "old" $ nf (maskPayload' (Just "\x00\x00\x00\x00")) kilo
]
, bgroup "full_mask"
[ bench "current" $ nf (maskPayload (Just "\xFF\xFF\xFF\xFF")) kilo
, bench "old" $ nf (maskPayload' (Just "\xFF\xFF\xFF\xFF")) kilo
]
, bgroup "one_byte_mask"
[ bench "current" $ nf (maskPayload (Just "\xCC\xCC\xCC\xCC")) kilo
, bench "old" $ nf (maskPayload' (Just "\xCC\xCC\xCC\xCC")) kilo
]
, bgroup "other_mask"
[ bench "current" $ nf (maskPayload (Just "\xB0\xA2\xB0\xA2")) kilo
, bench "old" $ nf (maskPayload' (Just "\xB0\xA2\xB0\xA2")) kilo
]
]
, bgroup "megabyte payload"
[ bgroup "zero_mask"
[ bench "current" $ nf (maskPayload (Just "\x00\x00\x00\x00")) mega
, bench "old" $ nf (maskPayload' (Just "\x00\x00\x00\x00")) mega
]
, bgroup "full_mask"
[ bench "current" $ nf (maskPayload (Just "\xFF\xFF\xFF\xFF")) mega
, bench "old" $ nf (maskPayload' (Just "\xFF\xFF\xFF\xFF")) mega
]
, bgroup "one_byte_mask"
[ bench "current" $ nf (maskPayload (Just "\xCC\xCC\xCC\xCC")) mega
, bench "old" $ nf (maskPayload' (Just "\xCC\xCC\xCC\xCC")) mega
]
, bgroup "other_mask"
[ bench "current" $ nf (maskPayload (Just "\xB0\xA2\xB0\xA2")) mega
, bench "old" $ nf (maskPayload' (Just "\xB0\xA2\xB0\xA2")) mega
]
]
]
]
16 changes: 12 additions & 4 deletions src/Network/WebSockets/Hybi13/Mask.hs
Expand Up @@ -2,6 +2,7 @@
-- | Masking of fragmes using a simple XOR algorithm
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# language OverloadedStrings #-}
module Network.WebSockets.Hybi13.Mask
( Mask
, maskPayload
Expand All @@ -24,11 +25,18 @@ type Mask = Maybe B.ByteString
--------------------------------------------------------------------------------
-- | Apply mask
maskPayload :: Mask -> BL.ByteString -> BL.ByteString
maskPayload Nothing = id
maskPayload (Just mask) = snd . BL.mapAccumL f (cycle $ B.unpack mask)
maskPayload Nothing = id
maskPayload (Just "\x00\x00\x00\x00") = id
maskPayload (Just mask) =
BL.fromChunks . go (cycle (B.unpack mask)) . BL.toChunks
where
f [] !c = ([], c)
f (m:ms) !c = (ms, m `xor` c)
go _ [] = []
go ms (chunk : chunks) =
let (ms', chunk') = B.mapAccumL f ms chunk
in chunk' : go ms' chunks
f (m : ms) c = (ms, m `xor` c)
f [] _ = error "impossible, we have infinite stream of mask bytes"


--------------------------------------------------------------------------------
-- | Create a random mask
Expand Down
10 changes: 10 additions & 0 deletions websockets.cabal
Expand Up @@ -156,3 +156,13 @@ Executable websockets-example
Source-repository head
Type: git
Location: https://github.com/jaspervdj/websockets

Benchmark bench-mask
type: exitcode-stdio-1.0
main-is: Mask.hs
hs-source-dirs: benchmarks, src
build-depends:
base,
bytestring,
criterion,
random

0 comments on commit 76a4e82

Please sign in to comment.