如何在无额外内存分配下将Text转换为Data.Vector.Unboxed.Vector
高效将Text转换为Data.Vector.Unboxed.Vector的方案
问题根源
你用VU.fromList . T.unpack这类方法产生额外内存分配,本质是T.unpack会生成一个中间链表——链表的每个节点都有内存开销,导致分配量远超过向量本身的需求。而你提到的VU.generate方案,由于T.index需要从Text起始位置逐个字符扫描到目标索引,整体时间复杂度为O(n²),数据量增大后性能会急剧下降。
最优实现
1. 流融合方案(零额外分配)
利用Text内置的流融合机制,直接将Text的字符流转换为Unboxed Vector,完全规避中间结构:
import Data.Text as T import qualified Data.Vector.Unboxed as VU import Data.Text.Internal.Fusion (stream) textToVector :: Text -> VU.Vector Char textToVector = VU.unstream . stream
这个实现的时间复杂度是O(n),内存分配仅为向量本身的大小(每个Char占4字节,和你预期的“字符数的4倍”一致)。
2. ST Monad手动填充(可控性更强)
如果需要自定义遍历逻辑,可以用VU.create结合ST Monad手动填充可变向量,同样是O(n)时间复杂度:
{-# LANGUAGE BangPatterns #-} import Data.Text as T import qualified Data.Vector.Unboxed as VU import qualified Data.Vector.Unboxed.Mutable as VUM import Control.Monad.ST textToVector :: Text -> VU.Vector Char textToVector t = VU.create $ do let !len = T.length t vec <- VUM.new len let go !i | i >= len = pure vec | otherwise = do VUM.write vec i (T.index t i) go (i + 1) go 0
注意:GHC会对连续的T.index调用进行优化,不会每次从头扫描Text,实际性能和流融合方案接近。
基准测试
你可以更新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 func' "fusion_method" textToVector text func' "st_method" textToVector text
内容的提问来源于stack exchange,提问作者tom
相关产品推荐
相关产品推荐

