diff --git a/ip.cabal b/ip.cabal
--- a/ip.cabal
+++ b/ip.cabal
@@ -1,5 +1,5 @@
 name:                ip
-version:             0.3
+version:             0.4
 synopsis:            Library for IP and MAC addresses
 description:         Please see README.md
 homepage:            https://github.com/andrewthad/ip#readme
@@ -16,8 +16,11 @@
   hs-source-dirs:      src
   exposed-modules:
     Net.Mac
+    Net.Mac.Text
+    Net.Mac.ByteString.Char8
     Net.IPv4
     Net.IPv4.Text
+    Net.IPv4.ByteString.Char8
     Net.Internal
   build-depends:       
       base >= 4.7 && < 5
@@ -25,7 +28,37 @@
     , aeson
     , hashable
     , text
+    , bytestring
+  ghc-options:         -Wall -O2 
   default-language:    Haskell2010
+
+test-suite ip-test
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Test.hs
+  build-depends:       
+      base
+    , ip
+    , test-framework
+    , test-framework-quickcheck2
+    , QuickCheck
+    , text
+    , bytestring
+  ghc-options:         -Wall -O2
+  default-language:    Haskell2010
+
+benchmark criterion
+  type:                exitcode-stdio-1.0
+  build-depends:
+      base
+    , ip
+    , criterion
+    , text
+    , bytestring
+  ghc-options:         -Wall -O2
+  default-language:    Haskell2010
+  hs-source-dirs:      test
+  main-is:             Bench.hs
 
 source-repository head
   type:     git
diff --git a/src/Net/IPv4.hs b/src/Net/IPv4.hs
--- a/src/Net/IPv4.hs
+++ b/src/Net/IPv4.hs
@@ -1,9 +1,37 @@
  {-# LANGUAGE DeriveGeneric #-}
  {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 
-module Net.IPv4 where
+{-| An IPv4 data type
+    
+    This module provides the IPv4 data type and functions for working
+    with it. There are also encoding and decoding functions provided
+    in this module, but they should be imported from 
+    @Net.IPv4.Text@ and @Net.IPv4.ByteString.Char8@ instead. They are
+    defined here so that the 'FromJSON' and 'ToJSON' instances can
+    use them.
 
-import Data.Text (Text)
+-}
+    
+module Net.IPv4 
+  ( -- * Types
+    IPv4(..)
+  , IPv4Range(..)
+    -- * Conversion Functions
+  , fromOctets
+  , fromOctets'
+  , toOctets
+    -- * Encoding and Decoding Functions
+  , fromDotDecimalText
+  , fromDotDecimalText'
+  , rangeFromDotDecimalText'
+  , dotDecimalRangeParser
+  , dotDecimalParser
+  , toDotDecimalText
+  , toDotDecimalBuilder
+  , rangeToDotDecimalText
+  , rangeToDotDecimalBuilder
+  ) where
+
 import qualified Data.Text.Lazy         as LText
 import qualified Data.Text.Lazy.Builder as TBuilder
 import Data.Text.Lazy.Builder.Int (decimal)
@@ -17,7 +45,16 @@
 import qualified Data.Aeson as Aeson
 import qualified Data.Aeson.Types as Aeson
 import qualified Data.Attoparsec.Text as AT
-import Net.Internal (attoparsecParseJSON)
+import Net.Internal (attoparsecParseJSON,rightToMaybe)
+import Control.Monad
+import Data.Text.Internal (Text(..))
+import Control.Monad.ST
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8  as BC8
+import qualified Data.ByteString        as ByteString
+import qualified Data.ByteString.Unsafe as ByteString
+import qualified Data.Text.Lazy.Builder as TBuilder
+import qualified Data.Text.Array        as TArray
 
 newtype IPv4 = IPv4 { getIPv4 :: Word32 }
   deriving (Eq,Ord,Show,Read,Enum,Bounded,Hashable,Generic)
@@ -43,9 +80,7 @@
     case rangeFromDotDecimalText' t of
       Left err  -> fail err
       Right res -> return res
-
-rightToMaybe :: Either a b -> Maybe b
-rightToMaybe = either (const Nothing) Just
+  parseJSON _ = mzero
 
 -- mask :: Int -> IPv4
 -- mask w = IPv4 $ complement $ 0xffffffff `shiftR` w
@@ -106,22 +141,24 @@
   .|. d
     )
 
+toOctets :: IPv4 -> (Word8,Word8,Word8,Word8)
+toOctets (IPv4 w) =
+  ( fromIntegral (shiftR w 24)
+  , fromIntegral (shiftR w 16)
+  , fromIntegral (shiftR w 8)
+  , fromIntegral w
+  )
+
 toDotDecimalText :: IPv4 -> Text
-toDotDecimalText = LText.toStrict . TBuilder.toLazyText . toDotDecimalBuilder
+toDotDecimalText = toTextPreAllocated
+{-# INLINE toDotDecimalText #-}
 
 -- It should be possible to write a more efficient version that initially
 -- allocates a block of strict text of length 15 and then starts filling 
 -- it in.
 toDotDecimalBuilder :: IPv4 -> TBuilder.Builder
-toDotDecimalBuilder (IPv4 w) = 
-  decimal (255 .&. shiftR w 24 )
-  <> dot
-  <> decimal (255 .&. shiftR w 16 )
-  <> dot
-  <> decimal (255 .&. shiftR w 8 )
-  <> dot
-  <> decimal (255 .&. w)
-  where dot = TBuilder.singleton '.'
+toDotDecimalBuilder = TBuilder.fromText . toTextPreAllocated
+{-# INLINE toDotDecimalBuilder #-}
 
 rangeToDotDecimalText :: IPv4Range -> Text
 rangeToDotDecimalText = LText.toStrict . TBuilder.toLazyText . rangeToDotDecimalBuilder
@@ -132,5 +169,79 @@
   <> TBuilder.singleton '/'
   <> decimal len
 
+toTextPreAllocated :: IPv4 -> Text
+toTextPreAllocated (IPv4 w) =
+  let w1 = fromIntegral $ 255 .&. shiftR w 24
+      w2 = fromIntegral $ 255 .&. shiftR w 16
+      w3 = fromIntegral $ 255 .&. shiftR w 8
+      w4 = fromIntegral $ 255 .&. w
+      dot = 46
+      (arr,len) = runST $ do
+        marr <- TArray.new 15
+        i1 <- putAndCount 0 w1 marr
+        let n1 = i1
+            n1' = i1 + 1
+        TArray.unsafeWrite marr n1 dot
+        i2 <- putAndCount n1' w2 marr
+        let n2 = i2 + n1'
+            n2' = n2 + 1
+        TArray.unsafeWrite marr n2 dot
+        i3 <- putAndCount n2' w3 marr
+        let n3 = i3 + n2'
+            n3' = n3 + 1
+        TArray.unsafeWrite marr n3 dot
+        i4 <- putAndCount n3' w4 marr
+        theArr <- TArray.unsafeFreeze marr
+        return (theArr,i4 + n3')
+  in Text arr 0 len
+    
+putAndCount :: Int -> Word8 -> TArray.MArray s -> ST s Int
+putAndCount pos w marr
+  | w < 10 = TArray.unsafeWrite marr pos (i2w w) >> return 1
+  | w < 100 = write2 pos w >> return 2
+  | otherwise = write3 pos w >> return 3
+  where
+  write2 off i0 = do
+    let i = fromIntegral i0; j = i + i
+    TArray.unsafeWrite marr off $ get2 j
+    TArray.unsafeWrite marr (off + 1) $ get2 (j + 1)
+  write3 off i0 = do
+    let i = fromIntegral i0; j = i + i + i
+    TArray.unsafeWrite marr off $ get3 j
+    TArray.unsafeWrite marr (off + 1) $ get3 (j + 1)
+    TArray.unsafeWrite marr (off + 2) $ get3 (j + 2)
+  get2 = fromIntegral . ByteString.unsafeIndex twoDigits
+  get3 = fromIntegral . ByteString.unsafeIndex threeDigits
 
+zero :: Word16
+zero = 48
+{-# INLINE zero #-}
+
+i2w :: (Integral a) => a -> Word16
+i2w v = zero + fromIntegral v
+{-# INLINE i2w #-}
+
+twoDigits :: ByteString
+twoDigits = BC8.pack
+  "0001020304050607080910111213141516171819\
+  \2021222324252627282930313233343536373839\
+  \4041424344454647484950515253545556575859\
+  \6061626364656667686970717273747576777879\
+  \8081828384858687888990919293949596979899"
+
+threeDigits :: ByteString
+threeDigits = 
+  ByteString.replicate 300 0 <> BC8.pack
+  "100101102103104105106107108109110111112\
+  \113114115116117118119120121122123124125\
+  \126127128129130131132133134135136137138\
+  \139140141142143144145146147148149150151\
+  \152153154155156157158159160161162163164\
+  \165166167168169170171172173174175176177\
+  \178179180181182183184185186187188189190\
+  \191192193194195196197198199200201202203\
+  \204205206207208209210211212213214215216\
+  \217218219220221222223224225226227228229\
+  \230231232233234235236237238239240241242\
+  \243244245246247248249250251252253254255"
 
diff --git a/src/Net/IPv4/ByteString/Char8.hs b/src/Net/IPv4/ByteString/Char8.hs
new file mode 100644
--- /dev/null
+++ b/src/Net/IPv4/ByteString/Char8.hs
@@ -0,0 +1,24 @@
+module Net.IPv4.ByteString.Char8
+  ( encode
+  , decode
+  , builder
+  , parser
+  ) where
+
+import Net.IPv4
+import Data.ByteString (ByteString)
+import Data.Attoparsec.Text (Parser)
+import Data.ByteString.Lazy.Builder (Builder)
+
+encode :: IPv4 -> ByteString
+encode = error "write me"
+
+decode :: ByteString -> Either String IPv4
+decode = error "write me"
+
+builder :: IPv4 -> Builder
+builder = error "write me"
+
+parser :: Parser IPv4
+parser = error "write me"
+
diff --git a/src/Net/IPv4/Text.hs b/src/Net/IPv4/Text.hs
--- a/src/Net/IPv4/Text.hs
+++ b/src/Net/IPv4/Text.hs
@@ -1,5 +1,29 @@
- {-# LANGUAGE DeriveGeneric #-}
- {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+module Net.IPv4.Text 
+  ( encode
+  , decode
+  , decodeEither
+  , builder
+  , parser
+  ) where
 
-module Net.IPv4.Text where
+import Net.IPv4
+import Net.Internal (rightToMaybe)
+import Data.Text (Text)
+import qualified Data.Attoparsec.Text as AT
+import qualified Data.Text.Lazy.Builder as TBuilder
+
+encode :: IPv4 -> Text
+encode = toDotDecimalText
+
+decodeEither :: Text -> Either String IPv4
+decodeEither = fromDotDecimalText'
+
+decode :: Text -> Maybe IPv4
+decode = rightToMaybe . decodeEither
+
+builder :: IPv4 -> TBuilder.Builder
+builder = toDotDecimalBuilder
+
+parser :: AT.Parser IPv4
+parser = dotDecimalParser
 
diff --git a/src/Net/Internal.hs b/src/Net/Internal.hs
--- a/src/Net/Internal.hs
+++ b/src/Net/Internal.hs
@@ -11,3 +11,7 @@
         Left err  -> fail err
         Right res -> return res
     _ -> fail "expected a String"
+
+rightToMaybe :: Either a b -> Maybe b
+rightToMaybe = either (const Nothing) Just
+
diff --git a/src/Net/Mac.hs b/src/Net/Mac.hs
--- a/src/Net/Mac.hs
+++ b/src/Net/Mac.hs
@@ -11,7 +11,7 @@
 import qualified Data.Attoparsec.Text as AT
 import qualified Data.Attoparsec.ByteString.Char8 as AB
 import Data.Bits ((.&.),(.|.),shiftR,shiftL,complement)
-import Net.Internal (attoparsecParseJSON)
+import Net.Internal (attoparsecParseJSON,rightToMaybe)
 import qualified Data.Text.Lazy.Builder as TBuilder
 import Data.Text.Lazy.Builder.Int (hexadecimal)
 import Data.Monoid ((<>))
@@ -34,6 +34,12 @@
 
 toText :: Mac -> Text
 toText = LText.toStrict . TBuilder.toLazyText . toTextBuilder
+
+fromText :: Text -> Maybe Mac
+fromText = rightToMaybe . fromText'
+
+fromText' :: Text -> Either String Mac
+fromText' t = AT.parseOnly (textParser <* AT.endOfInput) t
 
 toTextBuilder :: Mac -> TBuilder.Builder
 toTextBuilder (Mac a b) = 
diff --git a/src/Net/Mac/ByteString/Char8.hs b/src/Net/Mac/ByteString/Char8.hs
new file mode 100644
--- /dev/null
+++ b/src/Net/Mac/ByteString/Char8.hs
@@ -0,0 +1,24 @@
+module Net.Mac.ByteString.Char8
+  ( encode
+  , decode
+  , builder
+  , parser
+  ) where
+
+import Net.Mac
+import Data.ByteString (ByteString)
+import Data.Attoparsec.Text (Parser)
+import Data.ByteString.Lazy.Builder (Builder)
+
+encode :: Mac -> ByteString
+encode = error "write me"
+
+decode :: ByteString -> Either String Mac
+decode = error "write me"
+
+builder :: Mac -> Builder
+builder = error "write me"
+
+parser :: Parser Mac
+parser = error "write me"
+
diff --git a/src/Net/Mac/Text.hs b/src/Net/Mac/Text.hs
new file mode 100644
--- /dev/null
+++ b/src/Net/Mac/Text.hs
@@ -0,0 +1,29 @@
+module Net.Mac.Text 
+  ( encode
+  , decode
+  , decodeEither
+  , builder
+  , parser
+  ) where
+
+import Net.Mac
+import Data.Text (Text)
+import Net.Internal (rightToMaybe)
+import qualified Data.Attoparsec.Text as AT
+import qualified Data.Text.Lazy.Builder as TBuilder
+
+encode :: Mac -> Text
+encode = toText
+
+decodeEither :: Text -> Either String Mac
+decodeEither = fromText'
+
+decode :: Text -> Maybe Mac
+decode = rightToMaybe . decodeEither
+
+builder :: Mac -> TBuilder.Builder
+builder = toTextBuilder
+
+parser :: AT.Parser Mac
+parser = textParser
+
diff --git a/test/Bench.hs b/test/Bench.hs
new file mode 100644
--- /dev/null
+++ b/test/Bench.hs
@@ -0,0 +1,33 @@
+module Main (main) where
+
+import Naive
+import Criterion.Main
+import Net.IPv4 (IPv4(..))
+import Data.Bits ((.&.),(.|.),shiftR,shiftL,complement)
+import Data.Monoid ((<>))
+import Data.Text.Lazy.Builder.Int (decimal)
+import Data.Text.Internal (Text(..))
+import Data.Word
+import Data.ByteString (ByteString)
+import Control.Monad.ST
+import qualified Data.ByteString.Char8  as BC8
+import qualified Data.ByteString        as ByteString
+import qualified Data.ByteString.Unsafe as ByteString
+import qualified Data.Text.Lazy         as LText
+import qualified Data.Text.Lazy.Builder as TBuilder
+import qualified Data.Text.Array        as TArray
+import qualified Net.IPv4.Text as IPv4_Text
+
+main :: IO ()
+main = do
+  let ipAddr = IPv4 1000000009
+  defaultMain 
+    [ bgroup "IPv4 to Text" 
+      [ bench "Naive" $ whnf ipv4ToTextNaive ipAddr
+      , bench "Text Builder" $ whnf toDotDecimalText ipAddr
+      , bench "Preallocated" $ whnf toTextPreAllocated ipAddr
+      ]
+    , bgroup "IPv4 to ByteString" 
+      [ bench "Naive" $ whnf ipv4ToByteStringChar8Naive ipAddr
+      ]
+    ]
diff --git a/test/Test.hs b/test/Test.hs
new file mode 100644
--- /dev/null
+++ b/test/Test.hs
@@ -0,0 +1,53 @@
+ {-# LANGUAGE StandaloneDeriving         #-}
+ {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Main (main) where
+
+import Naive
+import Data.List                            (intercalate)
+import Test.QuickCheck                      (Gen, Arbitrary(..), choose)
+import Test.Framework                       (defaultMain, testGroup, Test)
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+
+import Net.IPv4 (IPv4(..),IPv4Range(..))
+import Net.Mac (Mac(..))
+import qualified Net.IPv4 as IPv4
+import qualified Net.IPv4.Text as IPv4_Text
+import qualified Net.Mac as Mac
+import qualified Net.Mac.Text as Mac_Text
+
+main :: IO ()
+main = defaultMain tests
+
+tests :: [Test]
+tests =
+  [ testGroup "Naive IPv4 encode/decode" 
+    [ testProperty "Isomorphism" 
+        $ propEncodeDecodeIso ipv4ToTextNaive ipv4FromTextNaive
+    ]
+  , testGroup "Text Builder IPv4 Text encode/decode"
+    [ testProperty "Identical to Naive"
+        $ propMatching toDotDecimalText ipv4ToTextNaive
+    ]
+  , testGroup "Raw byte array IPv4 Text encode/decode"
+    [ testProperty "Identical to Naive"
+        $ propMatching toTextPreAllocated ipv4ToTextNaive
+    ]
+  , testGroup "Performant MAC Text encode/decode"
+    [ testProperty "Isomorphism" 
+        $ propEncodeDecodeIso Mac_Text.encode Mac_Text.decode
+    ]
+  ]
+
+deriving instance Arbitrary IPv4
+
+instance Arbitrary Mac where
+  arbitrary = fmap fromTuple arbitrary
+    where fromTuple (a,b) = Mac a b
+
+propEncodeDecodeIso :: Eq a => (a -> b) -> (b -> Maybe a) -> a -> Bool
+propEncodeDecodeIso f g a = g (f a) == Just a
+
+propMatching :: Eq b => (a -> b) -> (a -> b) -> a -> Bool
+propMatching f g a = f a == g a
+
