我有一个关于将Text
转换为Data.Vector.Unboxed.Vector
的最有效方法的问题。我一直在使用像这样的weigh
基准测试工具进行测试,我看到了很多无关的分配:
{-# LANGUAGE BangPatterns #-}
module Main (main) where
import Control.Monad
import Data.String.Interpolate
import Data.Text as T
import qualified Data.Vector.Unboxed as VU
import Weigh
testFunc :: Int -> Text -> Weigh ()
testFunc inputSize text = wgroup [i|#{inputSize} characters|] $ do
func' "VU_fromList" VU.fromList (T.unpack text)
func' "VU_fromListN" (\t -> VU.fromListN (T.length t) (T.unpack t)) text
main :: IO ()
main = mainWith $
forM_ [10, 100, 1000, 10000, 100000] $ \n -> do
let !text = T.replicate n "0"
testFunc n text
字符串
在我的结果中,将10个字符转换为一个向量需要256字节的分配。转换10万个字符需要1 M字节。我认为这是因为我使用T.unpack
,编译器在内存中创建了中间列表元素。有没有一种方法可以只分配一个所需大小的向量,并在复制内容的同时遍历文本?
也许我可以使用Data.Vector.create函数吗?但是我找不到一种方法来遍历Text
的monadically。
完整的基准测试文件和结果在这里:
https://gist.github.com/thomasjm/7c2bd4f25ba4a75e90b898a902725ead
编辑:哦,我忘了说有一种方法是有效的,但它是二次时间。它是这样的:
generateMethod :: Text -> VU.Vector Char
generateMethod t = VU.generate (T.length t) (T.index t)
型
这个方法使用了大约4x个字节,其中x是字符数,正如您对UTF-8的期望。
1条答案
按热度按时间vvppvyoh1#
主要有两个问题:
T.uncons
不起作用,maybe和tuple在大多数情况下可以被优化掉,但剩余文本的分配通常不会被优化掉(尽管VU.unfoldr
似乎确实发生了这种情况)。VU.unfoldrN
似乎都在这个过程中增加了向量。我认为这是因为这种实现策略适合于融合,但这确实意味着你要牺牲一点效率。为了解决1,我选择了直接使用内部流表示,这确实允许您编写有效的遍历。
为了解决问题2,我选择了手动创建一个可变向量,你也已经考虑过了。
字符串
性能指标评测结果(使用
tasty-bench
和+RTS -T
,这是我首选的性能指标评测方法):型
(for内存使用情况,请查看分配的数量,而不是整个程序(包括多次迭代)运行过程中的峰值。)
Jon Purdy提出了使用
encodeUtf32LE
和unsafeFromForeignPtr
的解决方案,所以我也尝试了一下:型
但是,这一点的表现要差得多:
型
现在我已经创建了一个对
text
库的pull请求,它将foldlM
添加到了公共API:https://github.com/haskell/text/pull/543