packages feed

dataframe-fastcsv-1.4.1.0: src/DataFrame/IO/CSV/Fast/TextMerge.hs

{-# LANGUAGE BangPatterns #-}

{- | Parallel byte-level merge of per-chunk text columns: the byte copies
and offset rebase fan out over the chunk list, so a large text column does
not serialize the merge phase. Wraps the merged shared buffer + offsets as
'PackedText' (no 'Data.Text.Text' spine, no eager UTF-8 validation), exactly
as the pure 'mergeTextChunks' now produces.
-}
module DataFrame.IO.CSV.Fast.TextMerge (
    mergeTextChunksPar,
) where

import qualified Data.Text.Array as A
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector.Unboxed.Mutable as VUM

import Control.Monad (void)
import Control.Monad.ST (stToIO)

import Data.Int (Int32)
import DataFrame.IO.CSV.Fast.Workers (pooledRun)
import DataFrame.Internal.Column (Column (..))
import DataFrame.Internal.ColumnMerge (
    TextChunk (..),
    mergeTextChunks,
    spliceBitmaps,
    tcRows,
 )
import DataFrame.Internal.PackedText (mkPackedContiguous, mkPackedContiguous32)

{- | Merge text chunks with @width@-way parallel byte copies + offset
rebase, then wrap the shared buffer as 'PackedText'. Single chunks take
the pure (zero-copy) path.
-}
mergeTextChunksPar :: Int -> [TextChunk] -> IO Column
mergeTextChunksPar _ [c] = pure $! mergeTextChunks [c]
mergeTextChunksPar width cs = do
    let byteOffs = scanl (+) 0 (map tcUsed cs)
        rowOffs = scanl (+) 0 (map tcRows cs)
        totalBytes = last byteOffs
        totalRows = last rowOffs
    marr <- stToIO (A.new (max 1 totalBytes))
    -- Byte copy + offset rebase, parallel over chunks (disjoint ranges).
    let splice writeOff =
            void . pooledRun width $
                [ do
                    stToIO (A.copyI (tcUsed c) marr bOff (tcBytes c) 0)
                    let co = tcOffsets c
                        n = tcRows c
                        fill !i
                            | i > n = pure ()
                            | otherwise = do
                                writeOff (rOff + i) (bOff + VU.unsafeIndex co i)
                                fill (i + 1)
                    fill 1
                | (c, bOff, rOff) <- zip3 cs byteOffs rowOffs
                ]
    -- The final width is known before allocation: Int32 offsets whenever
    -- the merged buffer stays under 2^31 bytes (the common case).
    packed <-
        if totalBytes <= fromIntegral (maxBound :: Int32)
            then do
                offsMV <- VUM.unsafeNew (totalRows + 1) :: IO (VUM.IOVector Int32)
                VUM.unsafeWrite offsMV 0 0
                splice (\i v -> VUM.unsafeWrite offsMV i (fromIntegral v))
                mkPackedContiguous32
                    <$> stToIO (A.unsafeFreeze marr)
                    <*> VU.unsafeFreeze offsMV
            else do
                offsMV <- VUM.unsafeNew (totalRows + 1) :: IO (VUM.IOVector Int)
                VUM.unsafeWrite offsMV 0 0
                splice (VUM.unsafeWrite offsMV)
                mkPackedContiguous
                    <$> stToIO (A.unsafeFreeze marr)
                    <*> VU.unsafeFreeze offsMV
    let !bm = spliceBitmaps [(tcBitmap c, tcRows c) | c <- cs]
    pure (PackedText bm packed)