diff --git a/Data/Text.hs b/Data/Text.hs
--- a/Data/Text.hs
+++ b/Data/Text.hs
@@ -1,11 +1,11 @@
-{-# LANGUAGE BangPatterns, CPP, Rank2Types #-}
+{-# LANGUAGE BangPatterns, CPP, Rank2Types, UnboxedTuples #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 -- |
 -- Module      : Data.Text
--- Copyright   : (c) 2008, 2009 Tom Harper,
---               (c) 2009, 2010 Bryan O'Sullivan,
---               (c) 2009 Duncan Coutts
+-- Copyright   : (c) 2009, 2010, 2011 Bryan O'Sullivan,
+--               (c) 2009 Duncan Coutts,
+--               (c) 2008, 2009 Tom Harper
 --
 -- License     : BSD-style
 -- Maintainer  : bos@serpentine.com, rtomharper@googlemail.com,
@@ -208,6 +208,7 @@
 import qualified Data.Text.Fusion as S
 import qualified Data.Text.Fusion.Common as S
 import Data.Text.Fusion (stream, reverseStream, unstream)
+import Data.Text.Private (span_)
 import Data.Text.Internal (Text(..), empty, firstf, safe, text, textP)
 import qualified Prelude as P
 import Data.Text.Unsafe (Iter(..), iter, iter_, lengthWord16, reverseIter,
@@ -1081,11 +1082,8 @@
 -- of @t@ of elements that satisfy @p@, and whose second is the
 -- remainder of the list.
 span :: (Char -> Bool) -> Text -> (Text, Text)
-span p t@(Text arr off len) = (textP arr off k, textP arr (off+k) (len-k))
-  where k = loop 0
-        loop !i | i >= len || not (p c) = i
-                | otherwise             = loop (i+d)
-            where Iter c d              = iter t i
+span p t = case span_ p t of
+             (# hd,tl #) -> (hd,tl)
 {-# INLINE span #-}
 
 -- | /O(n)/ 'break' is like 'span', but the prefix returned is
@@ -1180,7 +1178,7 @@
 split p t = loop t
     where loop s | null s'   = [l]
                  | otherwise = l : loop (unsafeTail s')
-              where (l, s') = break p s
+              where (# l, s' #) = span_ (not . p) s
 {-# INLINE split #-}
 
 -- | /O(n)/ Splits a 'Text' into components of length @k@.  The last
@@ -1395,7 +1393,7 @@
          | otherwise = h : if null t
                            then []
                            else lines (unsafeTail t)
-    where (h,t) = span (/= '\n') ps
+    where (# h,t #) = span_ (/= '\n') ps
 {-# INLINE lines #-}
 
 {-
diff --git a/Data/Text/Encoding.hs b/Data/Text/Encoding.hs
--- a/Data/Text/Encoding.hs
+++ b/Data/Text/Encoding.hs
@@ -2,9 +2,9 @@
     UnliftedFFITypes #-}
 -- |
 -- Module      : Data.Text.Encoding
--- Copyright   : (c) 2008, 2009 Tom Harper,
---               (c) 2009, 2010 Bryan O'Sullivan,
---               (c) 2009 Duncan Coutts
+-- Copyright   : (c) 2009, 2010, 2011 Bryan O'Sullivan,
+--               (c) 2009 Duncan Coutts,
+--               (c) 2008, 2009 Tom Harper
 --
 -- License     : BSD-style
 -- Maintainer  : bos@serpentine.com, rtomharper@googlemail.com,
@@ -64,7 +64,7 @@
 import Foreign.C.Types (CSize)
 import Foreign.ForeignPtr (withForeignPtr)
 import Foreign.Marshal.Utils (with)
-import Foreign.Ptr (Ptr, plusPtr)
+import Foreign.Ptr (Ptr, minusPtr, plusPtr)
 import Foreign.Storable (peek, poke)
 import GHC.Base (MutableByteArray#)
 import System.IO.Unsafe (unsafePerformIO)
@@ -153,8 +153,9 @@
    where
     loop n1 m1 ptr = go n1 m1
      where
+      offLen = off + len
       go !n !m
-        | n == off+len = return (PS fp 0 m)
+        | n == offLen = return (PS fp 0 m)
         | otherwise = do
             let poke8 k v = poke (ptr `plusPtr` k) (fromIntegral v :: Word8)
                 ensure k act
@@ -167,7 +168,20 @@
                       start newSize n m fp'
                 {-# INLINE ensure #-}
             case A.unsafeIndex arr n of
-             w| w <= 0x7F  -> poke8 m w >> go (n+1) (m+1)
+             w| w <= 0x7F  -> ensure 1 $ do
+                  poke (ptr `plusPtr` m) (fromIntegral w :: Word8)
+                  -- A single ASCII octet is likely to start a run of
+                  -- them.  We see better performance when we
+                  -- special-case this assumption.
+                  let end = ptr `plusPtr` size
+                      ascii !t !u
+                        | t == offLen || u == end || v >= 0x80 =
+                            go t (u `minusPtr` ptr)
+                        | otherwise = do
+                            poke u (fromIntegral v :: Word8)
+                            ascii (t+1) (u `plusPtr` 1)
+                        where v = A.unsafeIndex arr t
+                  ascii (n+1) (ptr `plusPtr` (m+1))
               | w <= 0x7FF -> ensure 2 $ do
                   poke8 m     $ (w `shiftR` 6) + 0xC0
                   poke8 (m+1) $ (w .&. 0x3f) + 0x80
diff --git a/Data/Text/Lazy/Read.hs b/Data/Text/Lazy/Read.hs
--- a/Data/Text/Lazy/Read.hs
+++ b/Data/Text/Lazy/Read.hs
@@ -2,11 +2,10 @@
 
 -- |
 -- Module      : Data.Text.Lazy.Read
--- Copyright   : (c) 2010 Bryan O'Sullivan
+-- Copyright   : (c) 2010, 2011 Bryan O'Sullivan
 --
 -- License     : BSD-style
--- Maintainer  : bos@serpentine.com, rtomharper@googlemail.com,
---               duncan@haskell.org
+-- Maintainer  : bos@serpentine.com
 -- Stability   : experimental
 -- Portability : GHC
 --
@@ -23,8 +22,10 @@
 
 import Control.Monad (liftM)
 import Data.Char (isDigit, isHexDigit, ord)
+import Data.Int (Int8, Int16, Int32, Int64)
 import Data.Ratio ((%))
 import Data.Text.Lazy as T
+import Data.Word (Word, Word8, Word16, Word32, Word64)
 
 -- | Read some text.  If the read succeeds, return its value and the
 -- remaining text, otherwise an error message.
@@ -43,7 +44,16 @@
 -- 'Integer' for your result type.
 decimal :: Integral a => Reader a
 {-# SPECIALIZE decimal :: Reader Int #-}
+{-# SPECIALIZE decimal :: Reader Int8 #-}
+{-# SPECIALIZE decimal :: Reader Int16 #-}
+{-# SPECIALIZE decimal :: Reader Int32 #-}
+{-# SPECIALIZE decimal :: Reader Int64 #-}
 {-# SPECIALIZE decimal :: Reader Integer #-}
+{-# SPECIALIZE decimal :: Reader Word #-}
+{-# SPECIALIZE decimal :: Reader Word8 #-}
+{-# SPECIALIZE decimal :: Reader Word16 #-}
+{-# SPECIALIZE decimal :: Reader Word32 #-}
+{-# SPECIALIZE decimal :: Reader Word64 #-}
 decimal txt
     | T.null h  = Left "input does not start with a digit"
     | otherwise = Right (T.foldl' go 0 h, t)
@@ -71,8 +81,17 @@
  where (h,t) = T.splitAt 2 txt
 
 hex :: Integral a => Reader a
-{-# SPECIALIZE hex :: Reader Int #-}
-{-# SPECIALIZE hex :: Reader Integer #-}
+{-# SPECIALIZE hexadecimal :: Reader Int #-}
+{-# SPECIALIZE hexadecimal :: Reader Int8 #-}
+{-# SPECIALIZE hexadecimal :: Reader Int16 #-}
+{-# SPECIALIZE hexadecimal :: Reader Int32 #-}
+{-# SPECIALIZE hexadecimal :: Reader Int64 #-}
+{-# SPECIALIZE hexadecimal :: Reader Integer #-}
+{-# SPECIALIZE hexadecimal :: Reader Word #-}
+{-# SPECIALIZE hexadecimal :: Reader Word8 #-}
+{-# SPECIALIZE hexadecimal :: Reader Word16 #-}
+{-# SPECIALIZE hexadecimal :: Reader Word32 #-}
+{-# SPECIALIZE hexadecimal :: Reader Word64 #-}
 hex txt
     | T.null h  = Left "input does not start with a hexadecimal digit"
     | otherwise = Right (T.foldl' go 0 h, t)
@@ -114,7 +133,7 @@
 --
 -- >rational "3.foo" == Right (3.0, ".foo")
 -- >rational "3e"    == Right (3.0, "e")
-rational :: RealFloat a => Reader a
+rational :: Fractional a => Reader a
 {-# SPECIALIZE rational :: Reader Double #-}
 rational = floaty $ \real frac fracDenom -> fromRational $
                      real % 1 + frac % fracDenom
@@ -138,6 +157,10 @@
 
 signa :: Num a => Parser a -> Parser a
 {-# SPECIALIZE signa :: Parser Int -> Parser Int #-}
+{-# SPECIALIZE signa :: Parser Int8 -> Parser Int8 #-}
+{-# SPECIALIZE signa :: Parser Int16 -> Parser Int16 #-}
+{-# SPECIALIZE signa :: Parser Int32 -> Parser Int32 #-}
+{-# SPECIALIZE signa :: Parser Int64 -> Parser Int64 #-}
 {-# SPECIALIZE signa :: Parser Integer -> Parser Integer #-}
 signa p = do
   sign <- perhaps '+' $ char (\c -> c == '-' || c == '+')
@@ -168,7 +191,7 @@
 
 data T = T !Integer !Int
 
-floaty :: RealFloat a => (Integer -> Integer -> Integer -> a) -> Reader a
+floaty :: Fractional a => (Integer -> Integer -> Integer -> a) -> Reader a
 {-# INLINE floaty #-}
 floaty f = runP $ do
   sign <- perhaps '+' $ char (\c -> c == '-' || c == '+')
diff --git a/Data/Text/Private.hs b/Data/Text/Private.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/Private.hs
@@ -0,0 +1,28 @@
+{-# LANGUAGE BangPatterns, UnboxedTuples #-}
+
+-- |
+-- Module      : Data.Text.Private
+-- Copyright   : (c) 2011 Bryan O'Sullivan
+--
+-- License     : BSD-style
+-- Maintainer  : bos@serpentine.com
+-- Stability   : experimental
+-- Portability : GHC
+
+module Data.Text.Private
+    (
+      span_
+    ) where
+
+import Data.Text.Internal (Text(..), textP)
+import Data.Text.Unsafe (Iter(..), iter)
+
+span_ :: (Char -> Bool) -> Text -> (# Text, Text #)
+span_ p t@(Text arr off len) = (# hd,tl #)
+  where hd = textP arr off k
+        tl = textP arr (off+k) (len-k)
+        !k = loop 0
+        loop !i | i < len && p c = loop (i+d)
+                | otherwise      = i
+            where Iter c d       = iter t i
+{-# INLINE span_ #-}
diff --git a/Data/Text/Read.hs b/Data/Text/Read.hs
--- a/Data/Text/Read.hs
+++ b/Data/Text/Read.hs
@@ -1,12 +1,11 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE OverloadedStrings, UnboxedTuples #-}
 
 -- |
 -- Module      : Data.Text.Read
--- Copyright   : (c) 2010 Bryan O'Sullivan
+-- Copyright   : (c) 2010, 2011 Bryan O'Sullivan
 --
 -- License     : BSD-style
--- Maintainer  : bos@serpentine.com, rtomharper@googlemail.com,
---               duncan@haskell.org
+-- Maintainer  : bos@serpentine.com
 -- Stability   : experimental
 -- Portability : GHC
 --
@@ -23,8 +22,11 @@
 
 import Control.Monad (liftM)
 import Data.Char (isDigit, isHexDigit, ord)
+import Data.Int (Int8, Int16, Int32, Int64)
 import Data.Ratio ((%))
 import Data.Text as T
+import Data.Text.Private (span_)
+import Data.Word (Word, Word8, Word16, Word32, Word64)
 
 -- | Read some text.  If the read succeeds, return its value and the
 -- remaining text, otherwise an error message.
@@ -43,11 +45,20 @@
 -- 'Integer' for your result type.
 decimal :: Integral a => Reader a
 {-# SPECIALIZE decimal :: Reader Int #-}
+{-# SPECIALIZE decimal :: Reader Int8 #-}
+{-# SPECIALIZE decimal :: Reader Int16 #-}
+{-# SPECIALIZE decimal :: Reader Int32 #-}
+{-# SPECIALIZE decimal :: Reader Int64 #-}
 {-# SPECIALIZE decimal :: Reader Integer #-}
+{-# SPECIALIZE decimal :: Reader Word #-}
+{-# SPECIALIZE decimal :: Reader Word8 #-}
+{-# SPECIALIZE decimal :: Reader Word16 #-}
+{-# SPECIALIZE decimal :: Reader Word32 #-}
+{-# SPECIALIZE decimal :: Reader Word64 #-}
 decimal txt
     | T.null h  = Left "input does not start with a digit"
     | otherwise = Right (T.foldl' go 0 h, t)
-  where (h,t)  = T.span isDigit txt
+  where (# h,t #)  = span_ isDigit txt
         go n d = (n * 10 + fromIntegral (digitToInt d))
 
 -- | Read a hexadecimal integer, consisting of an optional leading
@@ -64,7 +75,16 @@
 -- 'Integer' for your result type.
 hexadecimal :: Integral a => Reader a
 {-# SPECIALIZE hexadecimal :: Reader Int #-}
+{-# SPECIALIZE hexadecimal :: Reader Int8 #-}
+{-# SPECIALIZE hexadecimal :: Reader Int16 #-}
+{-# SPECIALIZE hexadecimal :: Reader Int32 #-}
+{-# SPECIALIZE hexadecimal :: Reader Int64 #-}
 {-# SPECIALIZE hexadecimal :: Reader Integer #-}
+{-# SPECIALIZE hexadecimal :: Reader Word #-}
+{-# SPECIALIZE hexadecimal :: Reader Word8 #-}
+{-# SPECIALIZE hexadecimal :: Reader Word16 #-}
+{-# SPECIALIZE hexadecimal :: Reader Word32 #-}
+{-# SPECIALIZE hexadecimal :: Reader Word64 #-}
 hexadecimal txt
     | h == "0x" || h == "0X" = hex t
     | otherwise              = hex txt
@@ -72,11 +92,20 @@
 
 hex :: Integral a => Reader a
 {-# SPECIALIZE hex :: Reader Int #-}
+{-# SPECIALIZE hex :: Reader Int8 #-}
+{-# SPECIALIZE hex :: Reader Int16 #-}
+{-# SPECIALIZE hex :: Reader Int32 #-}
+{-# SPECIALIZE hex :: Reader Int64 #-}
 {-# SPECIALIZE hex :: Reader Integer #-}
+{-# SPECIALIZE hex :: Reader Word #-}
+{-# SPECIALIZE hex :: Reader Word8 #-}
+{-# SPECIALIZE hex :: Reader Word16 #-}
+{-# SPECIALIZE hex :: Reader Word32 #-}
+{-# SPECIALIZE hex :: Reader Word64 #-}
 hex txt
     | T.null h  = Left "input does not start with a hexadecimal digit"
     | otherwise = Right (T.foldl' go 0 h, t)
-  where (h,t)  = T.span isHexDigit txt
+  where (# h,t #)  = span_ isHexDigit txt
         go n d = (n * 16 + fromIntegral (hexDigitToInt d))
 
 hexDigitToInt :: Char -> Int
@@ -114,7 +143,7 @@
 --
 -- >rational "3.foo" == Right (3.0, ".foo")
 -- >rational "3e"    == Right (3.0, "e")
-rational :: RealFloat a => Reader a
+rational :: Fractional a => Reader a
 {-# SPECIALIZE rational :: Reader Double #-}
 rational = floaty $ \real frac fracDenom -> fromRational $
                      real % 1 + frac % fracDenom
@@ -138,6 +167,10 @@
 
 signa :: Num a => Parser a -> Parser a
 {-# SPECIALIZE signa :: Parser Int -> Parser Int #-}
+{-# SPECIALIZE signa :: Parser Int8 -> Parser Int8 #-}
+{-# SPECIALIZE signa :: Parser Int16 -> Parser Int16 #-}
+{-# SPECIALIZE signa :: Parser Int32 -> Parser Int32 #-}
+{-# SPECIALIZE signa :: Parser Int64 -> Parser Int64 #-}
 {-# SPECIALIZE signa :: Parser Integer -> Parser Integer #-}
 signa p = do
   sign <- perhaps '+' $ char (\c -> c == '-' || c == '+')
@@ -168,7 +201,7 @@
 
 data T = T !Integer !Int
 
-floaty :: RealFloat a => (Integer -> Integer -> Integer -> a) -> Reader a
+floaty :: Fractional a => (Integer -> Integer -> Integer -> a) -> Reader a
 {-# INLINE floaty #-}
 floaty f = runP $ do
   sign <- perhaps '+' $ char (\c -> c == '-' || c == '+')
diff --git a/text.cabal b/text.cabal
--- a/text.cabal
+++ b/text.cabal
@@ -1,5 +1,5 @@
 name:           text
-version:        0.11.1.11
+version:        0.11.1.12
 homepage:       https://github.com/bos/text
 bug-reports:    https://github.com/bos/text/issues
 synopsis:       An efficient packed Unicode text type.
@@ -100,6 +100,7 @@
     Data.Text.Lazy.Encoding.Fusion
     Data.Text.Lazy.Fusion
     Data.Text.Lazy.Search
+    Data.Text.Private
     Data.Text.Search
     Data.Text.Unsafe
     Data.Text.Unsafe.Base
