Oh noes! I was miscalculating the initial buffer size!
When performance testing encodeUtf8, I noticed that for some reason I was still seeing "ensure" show up in the profile, when I expected it shouldn't have been. Turns out I was using a "min" where I should have been using a "max", and thus allocating an initial bytestring that would almost always be too small, thus forcing reallocations and copying. Boo!
|@@ -144,7 +144,7 @@ decodeUtf8' = unsafePerformIO . try . evaluate . decodeUtf8With strictDecode|
|-- | Encode text using UTF-8 encoding.|
|encodeUtf8 :: Text -> ByteString|
|encodeUtf8 (Text arr off len) = unsafePerformIO $ do|
|- let size0 = min len 4|
|+ let size0 = max len 4|
|mallocByteString size0 >>= start size0 off 0|
|start size n0 m0 fp = withForeignPtr fp $ loop n0 m0|