From 85a8d8ef3af4ab7b972981d1aee9413f749b2d65 Mon Sep 17 00:00:00 2001 From: Michael Chavinda Date: Thu, 13 Aug 2026 23:51:40 -0700 Subject: [PATCH 1/8] fix: Do not hard cap runner memory at 30GB Rather cap relative to available memory so runs work in x-large env. --- haskell/VERSION | 2 +- haskell/exec.sh | 10 ++++++++++ haskell/haskell-benchmark.cabal | 10 +++++----- haskell/ver-haskell.sh | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/haskell/VERSION b/haskell/VERSION index f8d7555f..5f56c9fd 100644 --- a/haskell/VERSION +++ b/haskell/VERSION @@ -1 +1 @@ -3.1.0.0 +3.5.0.0 diff --git a/haskell/exec.sh b/haskell/exec.sh index 9d89133f..aa585ce6 100755 --- a/haskell/exec.sh +++ b/haskell/exec.sh @@ -24,4 +24,14 @@ fi source ./haskell/ver-haskell.sh [ -d "$HOME/.cache/cabal/packages/hackage.haskell.org" ] || cabal update + +## Set GHC's memory limit to 90% of the machine's memory so +## we exit gracefully. +if [ -r /proc/meminfo ]; then + MEM_KB=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo) +else + MEM_KB=$(( $(sysctl -n hw.memsize) / 1024 )) +fi +export GHCRTS="-M$(( MEM_KB * 9 / 10 / 1024 ))m" + cabal --project-dir=./haskell run -O2 "$1-haskell" diff --git a/haskell/haskell-benchmark.cabal b/haskell/haskell-benchmark.cabal index 6c641c05..bbcd0e74 100644 --- a/haskell/haskell-benchmark.cabal +++ b/haskell/haskell-benchmark.cabal @@ -7,11 +7,11 @@ common df-deps build-depends: base >= 4.7 && < 5 , bytestring >= 0.11 && < 0.13 , cassava >= 0.1 && < 1 - , dataframe-core ^>= 2.1 - , dataframe-parsing ^>= 2.1 - , dataframe-csv ^>= 2.1 - , dataframe-operations ^>= 2.1 - , dataframe-fastcsv ^>= 1.3 + , dataframe-core ^>= 2.4 + , dataframe-parsing ^>= 2.2 + , dataframe-csv ^>= 2.3 + , dataframe-operations ^>= 2.4 + , dataframe-fastcsv ^>= 1.4.1 , text >= 2.1 , vector >= 0.12 , time >= 1.9 diff --git a/haskell/ver-haskell.sh b/haskell/ver-haskell.sh index 680fd754..cdefc906 100755 --- a/haskell/ver-haskell.sh +++ b/haskell/ver-haskell.sh @@ -3,7 +3,7 @@ set -e cd haskell -export HASKELL_DF_VERSION="2.2.0.0" +export HASKELL_DF_VERSION="3.5.0.0" echo "${HASKELL_DF_VERSION}" > VERSION From 6557fc8e18ce0eba666fd1e97cde996dd73855a4 Mon Sep 17 00:00:00 2001 From: mchav Date: Thu, 20 Aug 2026 15:53:42 +0000 Subject: [PATCH 2/8] haskell: build against local ../dataframe, direct Q9 product sums, -N16 RTS flags, timeq eventlog markers Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KqVhiZ2cts6q2oS2mZkojp --- haskell/cabal.project | 5 +++++ haskell/groupby-haskell.hs | 12 ++++-------- haskell/haskell-benchmark.cabal | 2 +- haskell/timeq.hs | 8 +++++--- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/haskell/cabal.project b/haskell/cabal.project index f0c7b7ea..49e656e4 100644 --- a/haskell/cabal.project +++ b/haskell/cabal.project @@ -1,2 +1,7 @@ packages: . + /home/ubuntu/dataframe/dataframe-core + /home/ubuntu/dataframe/dataframe-parsing + /home/ubuntu/dataframe/dataframe-operations + /home/ubuntu/dataframe/dataframe-csv + /home/ubuntu/dataframe/dataframe-fastcsv allow-newer: all diff --git a/haskell/groupby-haskell.hs b/haskell/groupby-haskell.hs index 4b81addd..22899439 100755 --- a/haskell/groupby-haskell.hs +++ b/haskell/groupby-haskell.hs @@ -190,20 +190,16 @@ runBenchmark srcFile dataName machineType = do -- group count, then summed. Matches polars pl.corr(...)**2 then .sum(). runQuestion config - ( D.derive - "v2v2" - (dv2 * dv2) - (D.derive "v1v1" (dv1 * dv1) (D.derive "v1v2" (dv1 * dv2) df)) - ) + df "regression v1 v2 by id2 id4" ( D.groupBy [F.name id2, F.name id4] >>> D.aggregate [ "n" .= F.count v1 , "sx" .= F.sum dv1 , "sy" .= F.sum dv2 - , "sxy" .= F.sum (F.col @Double "v1v2") - , "sxx" .= F.sum (F.col @Double "v1v1") - , "syy" .= F.sum (F.col @Double "v2v2") + , "sxy" .= F.sum (dv1 * dv2) + , "sxx" .= F.sum (dv1 * dv1) + , "syy" .= F.sum (dv2 * dv2) ] >>> D.derive "r2" r2Expr >>> D.select [F.name id2, F.name id4, "r2"] diff --git a/haskell/haskell-benchmark.cabal b/haskell/haskell-benchmark.cabal index bbcd0e74..3b4b602b 100644 --- a/haskell/haskell-benchmark.cabal +++ b/haskell/haskell-benchmark.cabal @@ -19,7 +19,7 @@ common df-deps , directory >= 1.3 , unix default-language: Haskell2010 - ghc-options: -O2 -threaded -rtsopts "-with-rtsopts=-N8 -c -F1.5 -M30g -Fd1" + ghc-options: -O2 -threaded -rtsopts "-with-rtsopts=-N16 -A64m -n4m -I0 -c -F1.5 -M30g -Fd1" executable groupby-haskell import: df-deps diff --git a/haskell/timeq.hs b/haskell/timeq.hs index 5b526c65..a98e6072 100644 --- a/haskell/timeq.hs +++ b/haskell/timeq.hs @@ -24,15 +24,18 @@ import qualified DataFrame.Operations.Aggregation as D import qualified DataFrame.Operations.Core as D import qualified DataFrame.Operations.Subset as D import qualified DataFrame.Operations.Transformations as D -import System.Environment (getEnv) +import System.Environment (getEnv, lookupEnv) import Data.Time.Clock (getCurrentTime, diffUTCTime) +import Debug.Trace (traceMarkerIO) timeIt :: String -> IO Double -> IO () timeIt label act = do forM_ [1 :: Int, 2] $ \r -> do + traceMarkerIO (label ++ " run" ++ show r ++ " START") t0 <- getCurrentTime !chk <- act t1 <- getCurrentTime + traceMarkerIO (label ++ " run" ++ show r ++ " END") putStrLn $ label ++ " run" ++ show r ++ " " ++ show (realToFrac (diffUTCTime t1 t0) :: Double) ++ "s chk=" ++ show chk chkSumInt :: String -> DataFrame -> Double @@ -99,6 +102,5 @@ main = do timeIt "Q6" $ let r = (D.groupBy [F.name id4, F.name id5] >>> D.aggregate ["v3_median" .= F.median v3, "v3_sd" .= F.stddev v3]) df in run r [chkSumDbl "v3_median" r, chkSumDbl "v3_sd" r] timeIt "Q7" $ let r = (D.groupBy [F.name id3] >>> D.aggregate ["diff" .= F.maximum v1 - F.minimum v2]) df in run r [chkSumInt "diff" r] timeIt "Q8" $ let r = (D.groupBy [F.name id6] >>> D.aggregate ["largest2_v3" .= top2Sum v3]) df in run r [chkSumDbl "largest2_v3" r] - let df9 = D.derive "v2v2" (dv2*dv2) (D.derive "v1v1" (dv1*dv1) (D.derive "v1v2" (dv1*dv2) df)) - timeIt "Q9" $ let r = (D.groupBy [F.name id2, F.name id4] >>> D.aggregate ["n" .= F.count v1, "sx" .= F.sum dv1, "sy" .= F.sum dv2, "sxy" .= F.sum (F.col @Double "v1v2"), "sxx" .= F.sum (F.col @Double "v1v1"), "syy" .= F.sum (F.col @Double "v2v2")] >>> D.derive "r2" r2Expr) df9 in run r [chkSumDbl "r2" r] + timeIt "Q9" $ let r = (D.groupBy [F.name id2, F.name id4] >>> D.aggregate ["n" .= F.count v1, "sx" .= F.sum dv1, "sy" .= F.sum dv2, "sxy" .= F.sum (dv1*dv2), "sxx" .= F.sum (dv1*dv1), "syy" .= F.sum (dv2*dv2)] >>> D.derive "r2" r2Expr) df in run r [chkSumDbl "r2" r] timeIt "Q10" $ let r = (D.groupBy (map ((\i n -> i <> (T.pack . show) n) "id") [1..6]) >>> D.aggregate [F.sum v3 `F.as` "v3_sum"]) df in run r [chkSumDbl "v3_sum" r] From ef91b74427576f050475d1063a4aa06e2803b9f3 Mon Sep 17 00:00:00 2001 From: mchav Date: Thu, 20 Aug 2026 16:49:24 +0000 Subject: [PATCH 3/8] haskell: timeq grouping-only timers for Q3/Q5/Q9 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KqVhiZ2cts6q2oS2mZkojp --- haskell/timeq.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/haskell/timeq.hs b/haskell/timeq.hs index a98e6072..d9f4b8df 100644 --- a/haskell/timeq.hs +++ b/haskell/timeq.hs @@ -94,6 +94,9 @@ main = do timeIt "Q4grp" $ let gg = D.groupBy [F.name id4] df in (evaluate (VU.sum (rowToGroup gg)) >> pure 0) timeIt "Q4int" $ let r = D.aggregate ["v1_mean" .= F.mean v1, "v2_mean" .= F.mean v2] g4 in run r [chkSumDbl "v1_mean" r, chkSumDbl "v2_mean" r] + timeIt "Q3grp" $ let gg = D.groupBy [F.name id3] df in (evaluate (VU.sum (rowToGroup gg)) >> pure 0) + timeIt "Q5grp" $ let gg = D.groupBy [F.name id6] df in (evaluate (VU.sum (rowToGroup gg)) >> pure 0) + timeIt "Q9grp" $ let gg = D.groupBy [F.name id2, F.name id4] df in (evaluate (VU.sum (rowToGroup gg)) >> pure 0) timeIt "Q1" $ let r = (D.groupBy [F.name id1] >>> D.aggregate [F.sum v1 `F.as` "v1_sum"]) df in run r [chkSumInt "v1_sum" r] timeIt "Q2" $ let r = (D.groupBy [F.name id1, F.name id2] >>> D.aggregate ["v1_sum" .= F.sum v1]) df in run r [chkSumInt "v1_sum" r] timeIt "Q3" $ let r = (D.groupBy [F.name id3] >>> D.aggregate ["v1_sum" .= F.sum v1, "v3_mean" .= F.mean v3]) df in run r [chkSumInt "v1_sum" r, chkSumDbl "v3_mean" r] From 5b5e6baae5468b8a511ca3c39b0c464594dd1a54 Mon Sep 17 00:00:00 2001 From: mchav Date: Thu, 20 Aug 2026 19:19:44 +0000 Subject: [PATCH 4/8] haskell: Q8 emits polars-shaped exploded top-2 (2 rows/group); timeq deepseq-forces full results; microbench harness Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KqVhiZ2cts6q2oS2mZkojp --- haskell/groupby-haskell.hs | 37 ++++-- haskell/haskell-benchmark.cabal | 5 + haskell/microbench.hs | 228 ++++++++++++++++++++++++++++++++ haskell/timeq.hs | 23 +++- 4 files changed, 279 insertions(+), 14 deletions(-) create mode 100644 haskell/microbench.hs diff --git a/haskell/groupby-haskell.hs b/haskell/groupby-haskell.hs index 22899439..03c35a96 100755 --- a/haskell/groupby-haskell.hs +++ b/haskell/groupby-haskell.hs @@ -25,6 +25,7 @@ import DataFrame.Internal.Expression (AggStrategy (..), Expr (..)) import DataFrame.Schema (schemaType) import qualified DataFrame.Operations.Aggregation as D import qualified DataFrame.Operations.Core as D +import DataFrame.Operations.Merge () -- Semigroup DataFrame (row concat) import qualified DataFrame.Operations.Subset as D import qualified DataFrame.Operations.Transformations as D import System.Environment (getEnv, lookupEnv) @@ -175,15 +176,27 @@ runBenchmark srcFile dataName machineType = do (\res -> [chkSumInt "diff" res]) -- Q8: Largest two v3 by id6. - -- Polars takes top_k(2) per group then explodes and sums; the sum of the - -- exploded values equals the sum of (max + 2nd-max) per group, which is - -- what 'top2Sum' computes per group. Checksum parity is preserved. + -- Matches the polars reference shape: group_by(id6).agg(v3.top_k(2)).explode(v3) + -- -> TWO rows per group (2e6 rows x 2 cols on the 1e8 data). The per-group + -- max and second-max aggregate in one pass ('F.maximum' + 'top2Snd', both + -- exact multiset selections); the exploded frame is the vertical concat of + -- the max rows and the second-max rows (all maxes first, then all second + -- maxes — same multiset of rows as the reference, different row order). + -- chk = sum of v3 = the old top2Sum checksum. runQuestion config df "largest two v3 by id6" - (D.groupBy [F.name id6] >>> D.aggregate ["largest2_v3" .= top2Sum v3]) - (\res -> [chkSumDbl "largest2_v3" res]) + ( \d -> + let r = + ( D.groupBy [F.name id6] + >>> D.aggregate ["v3" .= F.maximum v3, "v3b" .= top2Snd v3] + ) + d + in D.select ["id6", "v3"] r + <> D.rename "v3b" "v3" (D.select ["id6", "v3b"] r) + ) + (\res -> [chkSumDbl "v3" res]) -- Q9: Regression (r^2 of v1 vs v2) by id2, id4. -- corr(v1,v2)^2 from the per-group sums of v1, v2, v1*v2, v1^2, v2^2 and the @@ -252,12 +265,18 @@ chkSumDbl col df = Right vec -> VU.sum vec Left _ -> 0.0 --- | Per-group sum of the two largest values (top_k(2) then sum, like polars Q8). -top2Sum :: Expr Double -> Expr Double -top2Sum = Agg (CollectAgg "top2Sum" f) +{- | Per-group second-largest value (with 'F.maximum' this yields polars Q8's +top_k(2) pair). The vectorized kernel path returns NaN for a group of size 1 +(the benchmark data has no size-1 id6 groups); this interpreter fallback +mirrors that. +-} +top2Snd :: Expr Double -> Expr Double +top2Snd = Agg (CollectAgg "top2Snd" f) where f :: V.Vector Double -> Double - f v = Prelude.sum (take 2 (L.sortBy (flip compare) (V.toList v))) + f v = case L.sortBy (flip compare) (V.toList v) of + (_ : b : _) -> b + _ -> 0 / 0 {- | Squared Pearson correlation of v1,v2 from per-group moment sums (polars Q9). Degenerate groups (zero variance) contribute 0 rather than poisoning the sum diff --git a/haskell/haskell-benchmark.cabal b/haskell/haskell-benchmark.cabal index 3b4b602b..ce66489a 100644 --- a/haskell/haskell-benchmark.cabal +++ b/haskell/haskell-benchmark.cabal @@ -38,3 +38,8 @@ executable timeq main-is: timeq.hs hs-source-dirs: . build-depends: deepseq + +executable microbench + import: df-deps + main-is: microbench.hs + hs-source-dirs: . diff --git a/haskell/microbench.hs b/haskell/microbench.hs new file mode 100644 index 00000000..b241a644 --- /dev/null +++ b/haskell/microbench.hs @@ -0,0 +1,228 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +{- | In-process synthetic micro-benchmarks for the wave-5 work: + +* keys: Q10-shaped key materialization — n rows, ~n groups over + 3 dict-encoded text keys (built via 'dictCompactColumn', the CSV reader's + path) + 3 Int keys; aggregate sum+count, then force ONLY the aggregate + columns vs forcing ALL columns (the 6 gathered key columns). +* q8: top2Snd/maximum exploded answer vs the old top2Sum checksum. +* q6: fused median+std wiring: grouping / placement / fused kernel split, + and fused vs median-only + std-only. +-} +import Control.Arrow ((>>>)) +import Control.Exception (evaluate) +import Control.Monad (forM_, when) +import qualified Data.List as L +import qualified Data.Text as T +import qualified Data.Text.Array as A +import qualified Data.Vector as V +import qualified Data.Vector.Unboxed as VU +import Data.Text.Encoding (encodeUtf8) +import qualified Data.ByteString as B +import Control.Monad.ST (runST) +import Data.Time.Clock (diffUTCTime, getCurrentTime) +import DataFrame.Functions ((.=)) +import qualified DataFrame.Functions as F +import DataFrame.Internal.Column (Column (..), forceColumn, fromUnboxedVector) +import DataFrame.Internal.DataFrame ( + DataFrame, + columnNames, + fromNamedColumns, + getColumn, + rowToGroup, + valueIndices, + ) +import DataFrame.Internal.DictEncode (dictCompactColumn) +import DataFrame.Internal.Expression (AggStrategy (..), Expr (..)) +import DataFrame.Internal.PackedText (mkPackedContiguous) +import qualified DataFrame.Operations.Aggregation as D +import qualified DataFrame.Operations.Core as D +import DataFrame.Operations.Merge () +import qualified DataFrame.Operations.Subset as D +import System.Environment (getArgs) + +timed :: String -> IO a -> IO (a, Double) +timed label act = do + t0 <- getCurrentTime + !r <- act + t1 <- getCurrentTime + let dt = realToFrac (diffUTCTime t1 t0) :: Double + putStrLn (label ++ ": " ++ show dt ++ "s") + pure (r, dt) + +-- | Contiguous packed-text column: row i = pool !! (i `mod` k), then compacted +-- through 'dictCompactColumn' (the CSV reader's canonical-dict path). +mkDictTextCol :: Int -> Int -> String -> Column +mkDictTextCol n k prefix = + let pool = [encodeUtf8 (T.pack (prefix ++ show j)) | j <- [0 .. k - 1]] + poolLens = VU.fromList (map B.length pool) + poolBytes = V.fromList (map B.unpack pool) + lenAt i = VU.unsafeIndex poolLens (i `mod` k) + offs = VU.scanl' (+) 0 (VU.generate n lenAt) + total = VU.last offs + arr = runST $ do + marr <- A.new (max 1 total) + let writeVal !pos [] = pos `seq` pure () + writeVal !pos (w : ws) = A.unsafeWrite marr pos w >> writeVal (pos + 1) ws + go !i + | i >= n = pure () + | otherwise = do + writeVal (VU.unsafeIndex offs i) (V.unsafeIndex poolBytes (i `mod` k)) + go (i + 1) + go 0 + A.unsafeFreeze marr + contiguous = PackedText Nothing (mkPackedContiguous arr offs) + in dictCompactColumn contiguous + +forceFrameColumns :: DataFrame -> [T.Text] -> IO () +forceFrameColumns df names = + forM_ names $ \nm -> case getColumn nm df of + Just c -> evaluate (forceColumn c) >> pure () + Nothing -> error ("missing column " ++ T.unpack nm) + +benchKeys :: Int -> IO () +benchKeys n = do + putStrLn ("== keys (Q10 shape), n=" ++ show n ++ " ==") + let kd1 = mkDictTextCol n 96 "id" + kd2 = mkDictTextCol n 96 "id0" + kd3 = mkDictTextCol n 90 "id00" + ki4 = fromUnboxedVector (VU.generate n (\i -> i `mod` 97 :: Int)) + ki5 = fromUnboxedVector (VU.generate n (\i -> (i `div` 97) `mod` 89 :: Int)) + ki6 = fromUnboxedVector (VU.generate n (\i -> i :: Int)) -- unique => ~n groups + v3 = fromUnboxedVector (VU.generate n (\i -> fromIntegral (i `mod` 1000) / 7 :: Double)) + df = + fromNamedColumns + [ ("id1", kd1) + , ("id2", kd2) + , ("id3", kd3) + , ("id4", ki4) + , ("id5", ki5) + , ("id6", ki6) + , ("v3", v3) + ] + _ <- evaluate (D.dimensions df) + forceFrameColumns df (columnNames df) -- source columns out of the timings + forM_ [1 :: Int, 2] $ \round_ -> do + putStrLn ("-- round " ++ show round_) + (g, _) <- timed "groupBy(6 keys) [force rowToGroup]" $ do + let gg = D.groupBy ["id1", "id2", "id3", "id4", "id5", "id6"] df + _ <- evaluate (VU.length (rowToGroup gg)) + pure gg + (r, tAgg) <- timed "aggregate sum+count [agg cols only]" $ do + let res = + D.aggregate + ["v3s" .= F.sum (F.col @Double "v3"), "cnt" .= F.count (F.col @Double "v3")] + g + forceFrameColumns res ["v3s", "cnt"] + pure res + (_, tKeys) <- timed "force 6 key columns" $ + forM_ ["id1", "id2", "id3", "id4", "id5", "id6"] $ \nm -> + timed (" force " ++ T.unpack nm) (forceFrameColumns r [nm]) + putStrLn + ( "forced-total/agg-only ratio: " + ++ show ((tAgg + tKeys) / tAgg) + ++ " (rows=" ++ show (fst (D.dimensions r)) ++ ")" + ) + +top2Sum :: Expr Double -> Expr Double +top2Sum = Agg (CollectAgg "top2Sum" f) + where + f v = Prelude.sum (take 2 (L.sortBy (flip compare) (V.toList v))) + +top2Snd :: Expr Double -> Expr Double +top2Snd = Agg (CollectAgg "top2Snd" f) + where + f v = case L.sortBy (flip compare) (V.toList v) of + (_ : b : _) -> b + _ -> 0 / 0 + +chkSumDbl :: T.Text -> DataFrame -> Double +chkSumDbl col df = case D.columnAsDoubleVector (F.col @Double col) df of + Right vec -> VU.sum vec + Left _ -> error "chkSumDbl" + +benchQ8 :: Int -> IO () +benchQ8 n = do + putStrLn ("== q8 (top2 explode), n=" ++ show n ++ " ==") + let nG = n `div` 100 + id6 = fromUnboxedVector (VU.generate n (\i -> i `mod` nG :: Int)) + v3 = fromUnboxedVector (VU.generate n (\i -> fromIntegral ((i * 7919) `mod` 100000) / 100 :: Double)) + df = fromNamedColumns [("id6", id6), ("v3", v3)] + v3e = F.col @Double "v3" + _ <- evaluate (D.dimensions df) + (oldAgg, _) <- timed "old top2Sum aggregate" $ do + let r = (D.groupBy ["id6"] >>> D.aggregate ["largest2_v3" .= top2Sum v3e]) df + _ <- evaluate (chkSumDbl "largest2_v3" r) + pure r + let oldChk = chkSumDbl "largest2_v3" oldAgg + ((newRes, newAgg), _) <- timed "new max+top2Snd exploded" $ do + let r = (D.groupBy ["id6"] >>> D.aggregate ["v3" .= F.maximum v3e, "v3b" .= top2Snd v3e]) df + ex = D.select ["id6", "v3"] r <> D.rename "v3b" "v3" (D.select ["id6", "v3b"] r) + _ <- evaluate (D.dimensions ex) + forceFrameColumns ex (columnNames ex) + pure (ex, r) + -- Per-group bitwise check: max_g + snd_g must equal the old top2Sum_g + -- exactly (identical accumulators; the old finalize is the single add). + let vOld = either (error . show) id (D.columnAsDoubleVector (F.col @Double "largest2_v3") oldAgg) + vMax = either (error . show) id (D.columnAsDoubleVector (F.col @Double "v3") newAgg) + vSnd = either (error . show) id (D.columnAsDoubleVector (F.col @Double "v3b") newAgg) + mismatches = VU.length (VU.filter id (VU.izipWith (\g m o -> m + VU.unsafeIndex vSnd g /= o) vMax vOld)) + putStrLn ("per-group (max+snd)==top2Sum bitwise mismatches: " ++ show mismatches) + when (mismatches /= 0) (error "per-group top2 mismatch") + let newChk = chkSumDbl "v3" newRes + (rows, cols) = D.dimensions newRes + putStrLn ("dims: " ++ show (rows, cols) ++ " (expect (" ++ show (2 * nG) ++ ",2))") + putStrLn ("oldChk=" ++ show oldChk ++ " newChk=" ++ show newChk ++ " equal=" ++ show (oldChk == newChk)) + when (rows /= 2 * nG || cols /= 2) (error "Q8 shape mismatch") + +benchQ6 :: Int -> IO () +benchQ6 n = do + putStrLn ("== q6 (median+std fused), n=" ++ show n ++ " ==") + let id4 = fromUnboxedVector (VU.generate n (\i -> i `mod` 100 :: Int)) + id5 = fromUnboxedVector (VU.generate n (\i -> (i `div` 100) `mod` 100 :: Int)) + v3 = fromUnboxedVector (VU.generate n (\i -> fromIntegral ((i * 2654435761) `mod` 1000003) / 997 :: Double)) + df = fromNamedColumns [("id4", id4), ("id5", id5), ("v3", v3)] + v3e = F.col @Double "v3" + _ <- evaluate (D.dimensions df) + forceFrameColumns df (columnNames df) -- source columns out of the timings + forM_ [1 :: Int, 2, 3] $ \round_ -> do + putStrLn ("-- round " ++ show round_) + (g, _) <- timed "groupBy [force rowToGroup]" $ do + let gg = D.groupBy ["id4", "id5"] df + _ <- evaluate (VU.length (rowToGroup gg)) + pure gg + _ <- timed "force valueIndices (placement)" $ evaluate (VU.length (valueIndices g)) + _ <- timed "aggregate median+std (fused kernel)" $ do + let r = D.aggregate ["v3_median" .= F.median v3e, "v3_sd" .= F.stddev v3e] g + forceFrameColumns r ["v3_median", "v3_sd"] + pure (chkSumDbl "v3_median" r + chkSumDbl "v3_sd" r) + _ <- timed "aggregate median only" $ do + let r = D.aggregate ["v3_median" .= F.median v3e] g + forceFrameColumns r ["v3_median"] + pure (chkSumDbl "v3_median" r) + _ <- timed "aggregate std only" $ do + let r = D.aggregate ["v3_sd" .= F.stddev v3e] g + forceFrameColumns r ["v3_sd"] + pure (chkSumDbl "v3_sd" r) + -- End-to-end (fresh grouping, like the driver's Q6) + _ <- timed "end-to-end groupBy+aggregate median+std" $ do + let r = (D.groupBy ["id4", "id5"] >>> D.aggregate ["v3_median" .= F.median v3e, "v3_sd" .= F.stddev v3e]) df + forceFrameColumns r ["v3_median", "v3_sd"] + pure (chkSumDbl "v3_median" r) + pure () + +main :: IO () +main = do + args <- getArgs + case args of + ["keys", ns] -> benchKeys (read ns) + ["q8", ns] -> benchQ8 (read ns) + ["q6", ns] -> benchQ6 (read ns) + _ -> do + benchQ8 5000000 + benchQ6 20000000 + benchKeys 20000000 diff --git a/haskell/timeq.hs b/haskell/timeq.hs index d9f4b8df..61917d60 100644 --- a/haskell/timeq.hs +++ b/haskell/timeq.hs @@ -22,6 +22,7 @@ import DataFrame.Internal.Expression (AggStrategy (..), Expr (..)) import DataFrame.Schema (schemaType) import qualified DataFrame.Operations.Aggregation as D import qualified DataFrame.Operations.Core as D +import DataFrame.Operations.Merge () -- Semigroup DataFrame (row concat) import qualified DataFrame.Operations.Subset as D import qualified DataFrame.Operations.Transformations as D import System.Environment (getEnv, lookupEnv) @@ -48,9 +49,14 @@ chkSumDbl col df = case D.columnAsDoubleVector (F.col @Double (T.pack col)) df o Right vec -> VU.sum vec Left _ -> 0.0 -top2Sum :: Expr Double -> Expr Double -top2Sum = Agg (CollectAgg "top2Sum" f) - where f v = Prelude.sum (take 2 (L.sortBy (flip compare) (V.toList v))) +-- | Per-group second-largest value; NaN for a size-1 group (kernel-matching +-- interpreter fallback; the benchmark data has no size-1 id6 groups). +top2Snd :: Expr Double -> Expr Double +top2Snd = Agg (CollectAgg "top2Snd" f) + where + f v = case L.sortBy (flip compare) (V.toList v) of + (_ : b : _) -> b + _ -> 0 / 0 r2Expr :: Expr Double r2Expr = @@ -61,9 +67,11 @@ r2Expr = den = (n * sxx - sx * sx) * (n * syy - sy * sy) in If (F.gt den (Lit 0)) ((num * num) / den) (Lit 0) +-- Force the ENTIRE result frame (all columns, keys included) so lazily-built +-- output columns are measured, matching what any real consumer pays. run :: DataFrame -> [Double] -> IO Double run df chks = do - _ <- evaluate (D.dimensions df) + _ <- evaluate (force df) evaluate (sum chks) main :: IO () @@ -104,6 +112,11 @@ main = do timeIt "Q5" $ let r = (D.groupBy [F.name id6] >>> D.aggregate ["v1_sum" .= F.sum v1, "v2_sum" .= F.sum v2, "v3_sum" .= F.sum v3]) df in run r [chkSumInt "v1_sum" r, chkSumInt "v2_sum" r, chkSumDbl "v3_sum" r] timeIt "Q6" $ let r = (D.groupBy [F.name id4, F.name id5] >>> D.aggregate ["v3_median" .= F.median v3, "v3_sd" .= F.stddev v3]) df in run r [chkSumDbl "v3_median" r, chkSumDbl "v3_sd" r] timeIt "Q7" $ let r = (D.groupBy [F.name id3] >>> D.aggregate ["diff" .= F.maximum v1 - F.minimum v2]) df in run r [chkSumInt "diff" r] - timeIt "Q8" $ let r = (D.groupBy [F.name id6] >>> D.aggregate ["largest2_v3" .= top2Sum v3]) df in run r [chkSumDbl "largest2_v3" r] + -- Q8 matches the polars answer shape: top_k(2) per group, exploded to two + -- rows per group (2e6 x 2). chk = sum of the exploded v3 column. + timeIt "Q8" $ let r = (D.groupBy [F.name id6] >>> D.aggregate ["v3" .= F.maximum v3, "v3b" .= top2Snd v3]) df + ex = D.select ["id6", "v3"] r <> D.rename "v3b" "v3" (D.select ["id6", "v3b"] r) + in run ex [chkSumDbl "v3" ex] timeIt "Q9" $ let r = (D.groupBy [F.name id2, F.name id4] >>> D.aggregate ["n" .= F.count v1, "sx" .= F.sum dv1, "sy" .= F.sum dv2, "sxy" .= F.sum (dv1*dv2), "sxx" .= F.sum (dv1*dv1), "syy" .= F.sum (dv2*dv2)] >>> D.derive "r2" r2Expr) df in run r [chkSumDbl "r2" r] timeIt "Q10" $ let r = (D.groupBy (map ((\i n -> i <> (T.pack . show) n) "id") [1..6]) >>> D.aggregate [F.sum v3 `F.as` "v3_sum"]) df in run r [chkSumDbl "v3_sum" r] + timeIt "Q10sc" $ let r = (D.groupBy (map ((\i n -> i <> (T.pack . show) n) "id") [1..6]) >>> D.aggregate ["v3" .= F.sum v3, "count" .= F.count v3]) df in run r [chkSumDbl "v3" r, chkSumInt "count" r] From 66d5a063fe3c6b1027fdaa994604bb02b1942f14 Mon Sep 17 00:00:00 2001 From: mchav Date: Thu, 20 Aug 2026 19:59:17 +0000 Subject: [PATCH 5/8] haskell: eventlog markers in runQuestion; timeq forces full results via show (matches official print cost) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KqVhiZ2cts6q2oS2mZkojp --- haskell/groupby-haskell.hs | 3 +++ haskell/timeq.hs | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/haskell/groupby-haskell.hs b/haskell/groupby-haskell.hs index 03c35a96..0c1b761c 100755 --- a/haskell/groupby-haskell.hs +++ b/haskell/groupby-haskell.hs @@ -5,6 +5,7 @@ import BenchmarkCommon import Control.Arrow ((>>>)) import Control.Monad (forM_) +import Debug.Trace (traceMarkerIO) import Data.Functor ((<&>)) import qualified Data.List as L import Data.Maybe (fromMaybe) @@ -240,12 +241,14 @@ runQuestion :: IO () runQuestion cfg inputDF qLabel transform chkFn = do forM_ [1, 2] $ \runNum -> do + traceMarkerIO (qLabel ++ " run" ++ show runNum ++ " START") (resultDF, calcTime) <- timeIt $ do let result = freshRun runNum transform inputDF print result return result memUsage <- getMemoryUsage let (outRows, outCols) = D.dimensions resultDF + traceMarkerIO (qLabel ++ " run" ++ show runNum ++ " END") (chkValues, chkTime) <- timeIt $ do let vals = chkFn resultDF print vals diff --git a/haskell/timeq.hs b/haskell/timeq.hs index 61917d60..ac69172a 100644 --- a/haskell/timeq.hs +++ b/haskell/timeq.hs @@ -5,7 +5,6 @@ import Control.Arrow ((>>>)) import Control.Monad (forM_) -import Control.DeepSeq (force) import Control.Exception (evaluate) import Data.Functor ((<&>)) import qualified Data.List as L @@ -68,10 +67,11 @@ r2Expr = in If (F.gt den (Lit 0)) ((num * num) / den) (Lit 0) -- Force the ENTIRE result frame (all columns, keys included) so lazily-built --- output columns are measured, matching what any real consumer pays. +-- output columns are measured, matching what the official harness's 'print +-- result' pays (Show forces every column thunk; rendering is capped at 20 rows). run :: DataFrame -> [Double] -> IO Double run df chks = do - _ <- evaluate (force df) + _ <- evaluate (length (show df)) evaluate (sum chks) main :: IO () From 30f9d7c1a9b2fcb1d789c7ba05bf5126c09e7f91 Mon Sep 17 00:00:00 2001 From: mchav Date: Fri, 21 Aug 2026 03:22:11 +0000 Subject: [PATCH 6/8] chore: Remove exploration and profiling files. --- haskell/cabal.project | 6 +- haskell/exec.sh | 13 +- haskell/groupby-haskell.hs | 10 +- haskell/haskell-benchmark.cabal | 11 -- haskell/join-haskell.hs | 13 +- haskell/microbench.hs | 228 -------------------------------- haskell/timeq.hs | 122 ----------------- logs.csv | 20 --- time.csv | 80 ----------- 9 files changed, 25 insertions(+), 478 deletions(-) delete mode 100644 haskell/microbench.hs delete mode 100644 haskell/timeq.hs diff --git a/haskell/cabal.project b/haskell/cabal.project index 49e656e4..40b8dd9d 100644 --- a/haskell/cabal.project +++ b/haskell/cabal.project @@ -1,7 +1,3 @@ packages: . - /home/ubuntu/dataframe/dataframe-core - /home/ubuntu/dataframe/dataframe-parsing - /home/ubuntu/dataframe/dataframe-operations - /home/ubuntu/dataframe/dataframe-csv - /home/ubuntu/dataframe/dataframe-fastcsv allow-newer: all +optimization: 2 diff --git a/haskell/exec.sh b/haskell/exec.sh index aa585ce6..6d517db5 100755 --- a/haskell/exec.sh +++ b/haskell/exec.sh @@ -25,8 +25,7 @@ source ./haskell/ver-haskell.sh [ -d "$HOME/.cache/cabal/packages/hackage.haskell.org" ] || cabal update -## Set GHC's memory limit to 90% of the machine's memory so -## we exit gracefully. +## Cap GHC's heap at 90% of machine memory so we exit gracefully. if [ -r /proc/meminfo ]; then MEM_KB=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo) else @@ -34,4 +33,14 @@ else fi export GHCRTS="-M$(( MEM_KB * 9 / 10 / 1024 ))m" +## Best-effort THP=always (GHC heap on 2MB pages); skipped when sudo is unavailable. +THP=/sys/kernel/mm/transparent_hugepage +if [ -w "$THP/enabled" ]; then + echo always > "$THP/enabled" || true + echo defer+madvise > "$THP/defrag" || true +elif sudo -n true 2>/dev/null; then + echo always | sudo tee "$THP/enabled" > /dev/null || true + echo defer+madvise | sudo tee "$THP/defrag" > /dev/null || true +fi + cabal --project-dir=./haskell run -O2 "$1-haskell" diff --git a/haskell/groupby-haskell.hs b/haskell/groupby-haskell.hs index 0c1b761c..208d772c 100755 --- a/haskell/groupby-haskell.hs +++ b/haskell/groupby-haskell.hs @@ -176,14 +176,8 @@ runBenchmark srcFile dataName machineType = do (D.groupBy [F.name id3] >>> D.aggregate ["diff" .= F.maximum v1 - F.minimum v2]) (\res -> [chkSumInt "diff" res]) - -- Q8: Largest two v3 by id6. - -- Matches the polars reference shape: group_by(id6).agg(v3.top_k(2)).explode(v3) - -- -> TWO rows per group (2e6 rows x 2 cols on the 1e8 data). The per-group - -- max and second-max aggregate in one pass ('F.maximum' + 'top2Snd', both - -- exact multiset selections); the exploded frame is the vertical concat of - -- the max rows and the second-max rows (all maxes first, then all second - -- maxes — same multiset of rows as the reference, different row order). - -- chk = sum of v3 = the old top2Sum checksum. + -- Q8: largest two v3 by id6, exploded to two rows per group + -- (polars reference shape: group_by(id6).agg(v3.top_k(2)).explode(v3)). runQuestion config df diff --git a/haskell/haskell-benchmark.cabal b/haskell/haskell-benchmark.cabal index ce66489a..37e85766 100644 --- a/haskell/haskell-benchmark.cabal +++ b/haskell/haskell-benchmark.cabal @@ -32,14 +32,3 @@ executable join-haskell main-is: join-haskell.hs hs-source-dirs: . other-modules: BenchmarkCommon - -executable timeq - import: df-deps - main-is: timeq.hs - hs-source-dirs: . - build-depends: deepseq - -executable microbench - import: df-deps - main-is: microbench.hs - hs-source-dirs: . diff --git a/haskell/join-haskell.hs b/haskell/join-haskell.hs index 6a196b0c..d00bf9d8 100644 --- a/haskell/join-haskell.hs +++ b/haskell/join-haskell.hs @@ -19,13 +19,15 @@ import DataFrame.IO.CSV ( defaultReadOptions, ) import DataFrame.IO.CSV.Fast (fastReadCsvWithOpts) -import DataFrame.Internal.DataFrame (DataFrame) +import DataFrame.Internal.Column (forceColumn) +import DataFrame.Internal.DataFrame (DataFrame, columnNames, getColumn) import DataFrame.Schema (SchemaType, schemaType) import qualified DataFrame.Operations.Core as D import qualified DataFrame.Operations.Join as DJ import Numeric (showEFloat) import System.Environment (getEnv, lookupEnv) import System.IO (BufferMode (..), hSetBuffering, stdout) +import System.Mem (performGC) main :: IO () main = do @@ -148,9 +150,10 @@ runJoin :: IO () runJoin cfg leftDF rightDF qLabel joinFn = do forM_ [1, 2] $ \runNum -> do + performGC (resultDF, calcTime) <- timeIt $ do let res = freshRun runNum (uncurry joinFn) (leftDF, rightDF) - _ <- evaluate res + forceAllColumns res return res memUsage <- getMemoryUsage @@ -167,6 +170,12 @@ runJoin cfg leftDF rightDF qLabel joinFn = do putStrLn $ qLabel ++ " completed." +forceAllColumns :: DataFrame -> IO () +forceAllColumns df = forM_ (columnNames df) $ \nm -> + case getColumn nm df of + Just c -> evaluate (forceColumn c) >> pure () + Nothing -> pure () + {- | Sum a Double column, skipping nulls/NaN. A left join surfaces the right-side value column as @Maybe Double@; 'columnAsDoubleVector' coerces nulls to NaN, so we drop NaN (matching polars sum, which skips nulls). diff --git a/haskell/microbench.hs b/haskell/microbench.hs deleted file mode 100644 index b241a644..00000000 --- a/haskell/microbench.hs +++ /dev/null @@ -1,228 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} - -{- | In-process synthetic micro-benchmarks for the wave-5 work: - -* keys: Q10-shaped key materialization — n rows, ~n groups over - 3 dict-encoded text keys (built via 'dictCompactColumn', the CSV reader's - path) + 3 Int keys; aggregate sum+count, then force ONLY the aggregate - columns vs forcing ALL columns (the 6 gathered key columns). -* q8: top2Snd/maximum exploded answer vs the old top2Sum checksum. -* q6: fused median+std wiring: grouping / placement / fused kernel split, - and fused vs median-only + std-only. --} -import Control.Arrow ((>>>)) -import Control.Exception (evaluate) -import Control.Monad (forM_, when) -import qualified Data.List as L -import qualified Data.Text as T -import qualified Data.Text.Array as A -import qualified Data.Vector as V -import qualified Data.Vector.Unboxed as VU -import Data.Text.Encoding (encodeUtf8) -import qualified Data.ByteString as B -import Control.Monad.ST (runST) -import Data.Time.Clock (diffUTCTime, getCurrentTime) -import DataFrame.Functions ((.=)) -import qualified DataFrame.Functions as F -import DataFrame.Internal.Column (Column (..), forceColumn, fromUnboxedVector) -import DataFrame.Internal.DataFrame ( - DataFrame, - columnNames, - fromNamedColumns, - getColumn, - rowToGroup, - valueIndices, - ) -import DataFrame.Internal.DictEncode (dictCompactColumn) -import DataFrame.Internal.Expression (AggStrategy (..), Expr (..)) -import DataFrame.Internal.PackedText (mkPackedContiguous) -import qualified DataFrame.Operations.Aggregation as D -import qualified DataFrame.Operations.Core as D -import DataFrame.Operations.Merge () -import qualified DataFrame.Operations.Subset as D -import System.Environment (getArgs) - -timed :: String -> IO a -> IO (a, Double) -timed label act = do - t0 <- getCurrentTime - !r <- act - t1 <- getCurrentTime - let dt = realToFrac (diffUTCTime t1 t0) :: Double - putStrLn (label ++ ": " ++ show dt ++ "s") - pure (r, dt) - --- | Contiguous packed-text column: row i = pool !! (i `mod` k), then compacted --- through 'dictCompactColumn' (the CSV reader's canonical-dict path). -mkDictTextCol :: Int -> Int -> String -> Column -mkDictTextCol n k prefix = - let pool = [encodeUtf8 (T.pack (prefix ++ show j)) | j <- [0 .. k - 1]] - poolLens = VU.fromList (map B.length pool) - poolBytes = V.fromList (map B.unpack pool) - lenAt i = VU.unsafeIndex poolLens (i `mod` k) - offs = VU.scanl' (+) 0 (VU.generate n lenAt) - total = VU.last offs - arr = runST $ do - marr <- A.new (max 1 total) - let writeVal !pos [] = pos `seq` pure () - writeVal !pos (w : ws) = A.unsafeWrite marr pos w >> writeVal (pos + 1) ws - go !i - | i >= n = pure () - | otherwise = do - writeVal (VU.unsafeIndex offs i) (V.unsafeIndex poolBytes (i `mod` k)) - go (i + 1) - go 0 - A.unsafeFreeze marr - contiguous = PackedText Nothing (mkPackedContiguous arr offs) - in dictCompactColumn contiguous - -forceFrameColumns :: DataFrame -> [T.Text] -> IO () -forceFrameColumns df names = - forM_ names $ \nm -> case getColumn nm df of - Just c -> evaluate (forceColumn c) >> pure () - Nothing -> error ("missing column " ++ T.unpack nm) - -benchKeys :: Int -> IO () -benchKeys n = do - putStrLn ("== keys (Q10 shape), n=" ++ show n ++ " ==") - let kd1 = mkDictTextCol n 96 "id" - kd2 = mkDictTextCol n 96 "id0" - kd3 = mkDictTextCol n 90 "id00" - ki4 = fromUnboxedVector (VU.generate n (\i -> i `mod` 97 :: Int)) - ki5 = fromUnboxedVector (VU.generate n (\i -> (i `div` 97) `mod` 89 :: Int)) - ki6 = fromUnboxedVector (VU.generate n (\i -> i :: Int)) -- unique => ~n groups - v3 = fromUnboxedVector (VU.generate n (\i -> fromIntegral (i `mod` 1000) / 7 :: Double)) - df = - fromNamedColumns - [ ("id1", kd1) - , ("id2", kd2) - , ("id3", kd3) - , ("id4", ki4) - , ("id5", ki5) - , ("id6", ki6) - , ("v3", v3) - ] - _ <- evaluate (D.dimensions df) - forceFrameColumns df (columnNames df) -- source columns out of the timings - forM_ [1 :: Int, 2] $ \round_ -> do - putStrLn ("-- round " ++ show round_) - (g, _) <- timed "groupBy(6 keys) [force rowToGroup]" $ do - let gg = D.groupBy ["id1", "id2", "id3", "id4", "id5", "id6"] df - _ <- evaluate (VU.length (rowToGroup gg)) - pure gg - (r, tAgg) <- timed "aggregate sum+count [agg cols only]" $ do - let res = - D.aggregate - ["v3s" .= F.sum (F.col @Double "v3"), "cnt" .= F.count (F.col @Double "v3")] - g - forceFrameColumns res ["v3s", "cnt"] - pure res - (_, tKeys) <- timed "force 6 key columns" $ - forM_ ["id1", "id2", "id3", "id4", "id5", "id6"] $ \nm -> - timed (" force " ++ T.unpack nm) (forceFrameColumns r [nm]) - putStrLn - ( "forced-total/agg-only ratio: " - ++ show ((tAgg + tKeys) / tAgg) - ++ " (rows=" ++ show (fst (D.dimensions r)) ++ ")" - ) - -top2Sum :: Expr Double -> Expr Double -top2Sum = Agg (CollectAgg "top2Sum" f) - where - f v = Prelude.sum (take 2 (L.sortBy (flip compare) (V.toList v))) - -top2Snd :: Expr Double -> Expr Double -top2Snd = Agg (CollectAgg "top2Snd" f) - where - f v = case L.sortBy (flip compare) (V.toList v) of - (_ : b : _) -> b - _ -> 0 / 0 - -chkSumDbl :: T.Text -> DataFrame -> Double -chkSumDbl col df = case D.columnAsDoubleVector (F.col @Double col) df of - Right vec -> VU.sum vec - Left _ -> error "chkSumDbl" - -benchQ8 :: Int -> IO () -benchQ8 n = do - putStrLn ("== q8 (top2 explode), n=" ++ show n ++ " ==") - let nG = n `div` 100 - id6 = fromUnboxedVector (VU.generate n (\i -> i `mod` nG :: Int)) - v3 = fromUnboxedVector (VU.generate n (\i -> fromIntegral ((i * 7919) `mod` 100000) / 100 :: Double)) - df = fromNamedColumns [("id6", id6), ("v3", v3)] - v3e = F.col @Double "v3" - _ <- evaluate (D.dimensions df) - (oldAgg, _) <- timed "old top2Sum aggregate" $ do - let r = (D.groupBy ["id6"] >>> D.aggregate ["largest2_v3" .= top2Sum v3e]) df - _ <- evaluate (chkSumDbl "largest2_v3" r) - pure r - let oldChk = chkSumDbl "largest2_v3" oldAgg - ((newRes, newAgg), _) <- timed "new max+top2Snd exploded" $ do - let r = (D.groupBy ["id6"] >>> D.aggregate ["v3" .= F.maximum v3e, "v3b" .= top2Snd v3e]) df - ex = D.select ["id6", "v3"] r <> D.rename "v3b" "v3" (D.select ["id6", "v3b"] r) - _ <- evaluate (D.dimensions ex) - forceFrameColumns ex (columnNames ex) - pure (ex, r) - -- Per-group bitwise check: max_g + snd_g must equal the old top2Sum_g - -- exactly (identical accumulators; the old finalize is the single add). - let vOld = either (error . show) id (D.columnAsDoubleVector (F.col @Double "largest2_v3") oldAgg) - vMax = either (error . show) id (D.columnAsDoubleVector (F.col @Double "v3") newAgg) - vSnd = either (error . show) id (D.columnAsDoubleVector (F.col @Double "v3b") newAgg) - mismatches = VU.length (VU.filter id (VU.izipWith (\g m o -> m + VU.unsafeIndex vSnd g /= o) vMax vOld)) - putStrLn ("per-group (max+snd)==top2Sum bitwise mismatches: " ++ show mismatches) - when (mismatches /= 0) (error "per-group top2 mismatch") - let newChk = chkSumDbl "v3" newRes - (rows, cols) = D.dimensions newRes - putStrLn ("dims: " ++ show (rows, cols) ++ " (expect (" ++ show (2 * nG) ++ ",2))") - putStrLn ("oldChk=" ++ show oldChk ++ " newChk=" ++ show newChk ++ " equal=" ++ show (oldChk == newChk)) - when (rows /= 2 * nG || cols /= 2) (error "Q8 shape mismatch") - -benchQ6 :: Int -> IO () -benchQ6 n = do - putStrLn ("== q6 (median+std fused), n=" ++ show n ++ " ==") - let id4 = fromUnboxedVector (VU.generate n (\i -> i `mod` 100 :: Int)) - id5 = fromUnboxedVector (VU.generate n (\i -> (i `div` 100) `mod` 100 :: Int)) - v3 = fromUnboxedVector (VU.generate n (\i -> fromIntegral ((i * 2654435761) `mod` 1000003) / 997 :: Double)) - df = fromNamedColumns [("id4", id4), ("id5", id5), ("v3", v3)] - v3e = F.col @Double "v3" - _ <- evaluate (D.dimensions df) - forceFrameColumns df (columnNames df) -- source columns out of the timings - forM_ [1 :: Int, 2, 3] $ \round_ -> do - putStrLn ("-- round " ++ show round_) - (g, _) <- timed "groupBy [force rowToGroup]" $ do - let gg = D.groupBy ["id4", "id5"] df - _ <- evaluate (VU.length (rowToGroup gg)) - pure gg - _ <- timed "force valueIndices (placement)" $ evaluate (VU.length (valueIndices g)) - _ <- timed "aggregate median+std (fused kernel)" $ do - let r = D.aggregate ["v3_median" .= F.median v3e, "v3_sd" .= F.stddev v3e] g - forceFrameColumns r ["v3_median", "v3_sd"] - pure (chkSumDbl "v3_median" r + chkSumDbl "v3_sd" r) - _ <- timed "aggregate median only" $ do - let r = D.aggregate ["v3_median" .= F.median v3e] g - forceFrameColumns r ["v3_median"] - pure (chkSumDbl "v3_median" r) - _ <- timed "aggregate std only" $ do - let r = D.aggregate ["v3_sd" .= F.stddev v3e] g - forceFrameColumns r ["v3_sd"] - pure (chkSumDbl "v3_sd" r) - -- End-to-end (fresh grouping, like the driver's Q6) - _ <- timed "end-to-end groupBy+aggregate median+std" $ do - let r = (D.groupBy ["id4", "id5"] >>> D.aggregate ["v3_median" .= F.median v3e, "v3_sd" .= F.stddev v3e]) df - forceFrameColumns r ["v3_median", "v3_sd"] - pure (chkSumDbl "v3_median" r) - pure () - -main :: IO () -main = do - args <- getArgs - case args of - ["keys", ns] -> benchKeys (read ns) - ["q8", ns] -> benchQ8 (read ns) - ["q6", ns] -> benchQ6 (read ns) - _ -> do - benchQ8 5000000 - benchQ6 20000000 - benchKeys 20000000 diff --git a/haskell/timeq.hs b/haskell/timeq.hs deleted file mode 100644 index ac69172a..00000000 --- a/haskell/timeq.hs +++ /dev/null @@ -1,122 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE BangPatterns #-} - -import Control.Arrow ((>>>)) -import Control.Monad (forM_) -import Control.Exception (evaluate) -import Data.Functor ((<&>)) -import qualified Data.List as L -import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Vector as V -import qualified Data.Vector.Unboxed as VU -import DataFrame.Functions ((.=)) -import qualified DataFrame.Functions as F -import DataFrame.IO.CSV (ReadOptions (..), TypeSpec (..), defaultReadOptions) -import DataFrame.IO.CSV.Fast -import DataFrame.Internal.DataFrame (DataFrame, rowToGroup) -import DataFrame.Internal.Expression (AggStrategy (..), Expr (..)) -import DataFrame.Schema (schemaType) -import qualified DataFrame.Operations.Aggregation as D -import qualified DataFrame.Operations.Core as D -import DataFrame.Operations.Merge () -- Semigroup DataFrame (row concat) -import qualified DataFrame.Operations.Subset as D -import qualified DataFrame.Operations.Transformations as D -import System.Environment (getEnv, lookupEnv) -import Data.Time.Clock (getCurrentTime, diffUTCTime) -import Debug.Trace (traceMarkerIO) - -timeIt :: String -> IO Double -> IO () -timeIt label act = do - forM_ [1 :: Int, 2] $ \r -> do - traceMarkerIO (label ++ " run" ++ show r ++ " START") - t0 <- getCurrentTime - !chk <- act - t1 <- getCurrentTime - traceMarkerIO (label ++ " run" ++ show r ++ " END") - putStrLn $ label ++ " run" ++ show r ++ " " ++ show (realToFrac (diffUTCTime t1 t0) :: Double) ++ "s chk=" ++ show chk - -chkSumInt :: String -> DataFrame -> Double -chkSumInt col df = case D.columnAsIntVector (F.col @Int (T.pack col)) df of - Right vec -> fromIntegral $ VU.sum vec - Left _ -> 0.0 - -chkSumDbl :: String -> DataFrame -> Double -chkSumDbl col df = case D.columnAsDoubleVector (F.col @Double (T.pack col)) df of - Right vec -> VU.sum vec - Left _ -> 0.0 - --- | Per-group second-largest value; NaN for a size-1 group (kernel-matching --- interpreter fallback; the benchmark data has no size-1 id6 groups). -top2Snd :: Expr Double -> Expr Double -top2Snd = Agg (CollectAgg "top2Snd" f) - where - f v = case L.sortBy (flip compare) (V.toList v) of - (_ : b : _) -> b - _ -> 0 / 0 - -r2Expr :: Expr Double -r2Expr = - let n = F.toDouble (F.col @Int "n") - sx = F.col @Double "sx"; sy = F.col @Double "sy" - sxy = F.col @Double "sxy"; sxx = F.col @Double "sxx"; syy = F.col @Double "syy" - num = n * sxy - sx * sy - den = (n * sxx - sx * sx) * (n * syy - sy * sy) - in If (F.gt den (Lit 0)) ((num * num) / den) (Lit 0) - --- Force the ENTIRE result frame (all columns, keys included) so lazily-built --- output columns are measured, matching what the official harness's 'print --- result' pays (Show forces every column thunk; rendering is capped at 20 rows). -run :: DataFrame -> [Double] -> IO Double -run df chks = do - _ <- evaluate (length (show df)) - evaluate (sum chks) - -main :: IO () -main = do - dataName <- getEnv "SRC_DATANAME" - let srcFile = "./data/" ++ dataName ++ ".csv" - let id1 = F.col @Text "id1"; id2 = F.col @Text "id2"; id3 = F.col @Text "id3" - id4 = F.col @Int "id4"; id5 = F.col @Int "id5"; id6 = F.col @Int "id6" - v1 = F.col @Int "v1"; v2 = F.col @Int "v2"; v3 = F.col @Double "v3" - dv1 = F.toDouble v1; dv2 = F.toDouble v2 - df <- fastReadCsvWithOpts - (defaultReadOptions { typeSpec = SpecifyTypes - [ (F.name id1, schemaType @Text), (F.name id2, schemaType @Text) - , (F.name id3, schemaType @Text), (F.name id4, schemaType @Int) - , (F.name id5, schemaType @Int), (F.name id6, schemaType @Int) - , (F.name v1, schemaType @Int), (F.name v2, schemaType @Int) - , (F.name v3, schemaType @Double) ] NoInference }) srcFile - _ <- evaluate (D.dimensions df) - putStrLn $ "loaded " ++ show (D.dimensions df) - - -- Aggregate-only timing for Q1/Q4: pre-build & force the grouping, then time aggregate. - let g1 = D.groupBy [F.name id1] df - _ <- evaluate (VU.sum (rowToGroup g1)) - timeIt "Q1agg" $ let r = D.aggregate [F.sum v1 `F.as` "v1_sum"] g1 in run r [chkSumInt "v1_sum" r] - let g4 = D.groupBy [F.name id4] df - _ <- evaluate (VU.sum (rowToGroup g4)) - timeIt "Q4agg" $ let r = D.aggregate ["v1_mean" .= F.mean v1, "v2_mean" .= F.mean v2, "v3_mean" .= F.mean v3] g4 in run r [chkSumDbl "v1_mean" r, chkSumDbl "v2_mean" r, chkSumDbl "v3_mean" r] - timeIt "Q4grp" $ let gg = D.groupBy [F.name id4] df in (evaluate (VU.sum (rowToGroup gg)) >> pure 0) - timeIt "Q4int" $ let r = D.aggregate ["v1_mean" .= F.mean v1, "v2_mean" .= F.mean v2] g4 in run r [chkSumDbl "v1_mean" r, chkSumDbl "v2_mean" r] - - timeIt "Q3grp" $ let gg = D.groupBy [F.name id3] df in (evaluate (VU.sum (rowToGroup gg)) >> pure 0) - timeIt "Q5grp" $ let gg = D.groupBy [F.name id6] df in (evaluate (VU.sum (rowToGroup gg)) >> pure 0) - timeIt "Q9grp" $ let gg = D.groupBy [F.name id2, F.name id4] df in (evaluate (VU.sum (rowToGroup gg)) >> pure 0) - timeIt "Q1" $ let r = (D.groupBy [F.name id1] >>> D.aggregate [F.sum v1 `F.as` "v1_sum"]) df in run r [chkSumInt "v1_sum" r] - timeIt "Q2" $ let r = (D.groupBy [F.name id1, F.name id2] >>> D.aggregate ["v1_sum" .= F.sum v1]) df in run r [chkSumInt "v1_sum" r] - timeIt "Q3" $ let r = (D.groupBy [F.name id3] >>> D.aggregate ["v1_sum" .= F.sum v1, "v3_mean" .= F.mean v3]) df in run r [chkSumInt "v1_sum" r, chkSumDbl "v3_mean" r] - timeIt "Q4" $ let r = (D.groupBy [F.name id4] >>> D.aggregate ["v1_mean" .= F.mean v1, "v2_mean" .= F.mean v2, "v3_mean" .= F.mean v3]) df in run r [chkSumDbl "v1_mean" r, chkSumDbl "v2_mean" r, chkSumDbl "v3_mean" r] - timeIt "Q5" $ let r = (D.groupBy [F.name id6] >>> D.aggregate ["v1_sum" .= F.sum v1, "v2_sum" .= F.sum v2, "v3_sum" .= F.sum v3]) df in run r [chkSumInt "v1_sum" r, chkSumInt "v2_sum" r, chkSumDbl "v3_sum" r] - timeIt "Q6" $ let r = (D.groupBy [F.name id4, F.name id5] >>> D.aggregate ["v3_median" .= F.median v3, "v3_sd" .= F.stddev v3]) df in run r [chkSumDbl "v3_median" r, chkSumDbl "v3_sd" r] - timeIt "Q7" $ let r = (D.groupBy [F.name id3] >>> D.aggregate ["diff" .= F.maximum v1 - F.minimum v2]) df in run r [chkSumInt "diff" r] - -- Q8 matches the polars answer shape: top_k(2) per group, exploded to two - -- rows per group (2e6 x 2). chk = sum of the exploded v3 column. - timeIt "Q8" $ let r = (D.groupBy [F.name id6] >>> D.aggregate ["v3" .= F.maximum v3, "v3b" .= top2Snd v3]) df - ex = D.select ["id6", "v3"] r <> D.rename "v3b" "v3" (D.select ["id6", "v3b"] r) - in run ex [chkSumDbl "v3" ex] - timeIt "Q9" $ let r = (D.groupBy [F.name id2, F.name id4] >>> D.aggregate ["n" .= F.count v1, "sx" .= F.sum dv1, "sy" .= F.sum dv2, "sxy" .= F.sum (dv1*dv2), "sxx" .= F.sum (dv1*dv1), "syy" .= F.sum (dv2*dv2)] >>> D.derive "r2" r2Expr) df in run r [chkSumDbl "r2" r] - timeIt "Q10" $ let r = (D.groupBy (map ((\i n -> i <> (T.pack . show) n) "id") [1..6]) >>> D.aggregate [F.sum v3 `F.as` "v3_sum"]) df in run r [chkSumDbl "v3_sum" r] - timeIt "Q10sc" $ let r = (D.groupBy (map ((\i n -> i <> (T.pack . show) n) "id") [1..6]) >>> D.aggregate ["v3" .= F.sum v3, "count" .= F.count v3]) df in run r [chkSumDbl "v3" r, chkSumInt "count" r] diff --git a/logs.csv b/logs.csv index fe94c5ed..66920833 100644 --- a/logs.csv +++ b/logs.csv @@ -7392,23 +7392,3 @@ ip-172-31-18-189,1784046154,duckdb,1.5.4,08e34c447b,groupby,G1_1e9_1e2_5_0,17840 ip-172-31-18-189,1784046154,duckdb,1.5.4,08e34c447b,groupby,G1_1e9_1e2_5_0,1784053271.16982,finish,0,0,c6id.4xlarge ip-172-31-18-189,1784046154,duckdb,1.5.4,08e34c447b,join,J1_1e9_NA_0_0,1784053286.1849,start,,,c6id.4xlarge ip-172-31-18-189,1784046154,duckdb,1.5.4,08e34c447b,join,J1_1e9_NA_0_0,1784055922.71045,finish,0,0,c6id.4xlarge -ip-172-31-18-189,1786373405,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e7_1e2_0_0,1786373407.63123,start,,,c6id.4xlarge -ip-172-31-18-189,1786373405,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e7_1e2_0_0,1786373435.22305,finish,7,0,c6id.4xlarge -ip-172-31-18-189,1786373405,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e7_1e1_0_0,1786373450.23815,start,,,c6id.4xlarge -ip-172-31-18-189,1786373405,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e7_1e1_0_0,1786373477.74633,finish,7,0,c6id.4xlarge -ip-172-31-18-189,1786373405,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e7_2e0_0_0,1786373492.76142,start,,,c6id.4xlarge -ip-172-31-18-189,1786373405,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e7_2e0_0_0,1786373527.4555,finish,11,0,c6id.4xlarge -ip-172-31-18-189,1786373405,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e7_1e2_0_1,1786373542.47061,start,,,c6id.4xlarge -ip-172-31-18-189,1786373405,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e7_1e2_0_1,1786373567.22655,finish,7,0,c6id.4xlarge -ip-172-31-18-189,1786373405,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e7_1e2_5_0,1786373582.24164,start,,,c6id.4xlarge -ip-172-31-18-189,1786373405,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e7_1e2_5_0,1786373582.84261,finish,8,0,c6id.4xlarge -ip-172-31-18-189,1786373702,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e8_1e2_0_0,1786373703.5071,start,,,c6id.4xlarge -ip-172-31-18-189,1786373702,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e8_1e2_0_0,1786373734.20329,finish,10,251,c6id.4xlarge -ip-172-31-18-189,1786373702,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e8_1e1_0_0,1786373749.21738,start,,,c6id.4xlarge -ip-172-31-18-189,1786373702,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e8_1e1_0_0,1786373779.22521,finish,10,251,c6id.4xlarge -ip-172-31-18-189,1786373702,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e8_2e0_0_0,1786373794.24029,start,,,c6id.4xlarge -ip-172-31-18-189,1786373702,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e8_2e0_0_0,1786373824.67766,finish,14,251,c6id.4xlarge -ip-172-31-18-189,1786373702,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e8_1e2_0_1,1786373839.69275,start,,,c6id.4xlarge -ip-172-31-18-189,1786373702,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e8_1e2_0_1,1786373869.93667,finish,10,251,c6id.4xlarge -ip-172-31-18-189,1786373702,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e8_1e2_5_0,1786373884.95176,start,,,c6id.4xlarge -ip-172-31-18-189,1786373702,haskell,2.2.0.0,dataframe-2.2.0.0,groupby,G1_1e8_1e2_5_0,1786373885.54634,finish,8,0,c6id.4xlarge diff --git a/time.csv b/time.csv index 54e67377..d1e80a5d 100644 --- a/time.csv +++ b/time.csv @@ -48731,83 +48731,3 @@ ip-172-31-18-189,1784046154,1784054881.6714952,join,J1_1e9_NA_0_0,1000000000,med ip-172-31-18-189,1784046154,1784054976.2629597,join,J1_1e9_NA_0_0,1000000000,medium inner on factor,899989698,11,duckdb,1.5.4,08e34c447b,inner_join,2,92.045,20.581,TRUE,44998219781.543;44956313962.671,1.839,,True,c6id.4xlarge ip-172-31-18-189,1784046154,1784055440.1588929,join,J1_1e9_NA_0_0,1000000000,big inner on int,900000000,13,duckdb,1.5.4,08e34c447b,inner_join,1,459.094,15.13,TRUE,44999013966.479;45000583181.582,3.927,,True,c6id.4xlarge ip-172-31-18-189,1784046154,1784055915.594259,join,J1_1e9_NA_0_0,1000000000,big inner on int,900000000,13,duckdb,1.5.4,08e34c447b,inner_join,2,470.841,15.823,TRUE,44999013966.478;45000583181.578,3.623,,True,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373412.15237,groupby,G1_1e7_1e2_0_0,10000000,sum v1 by id1,100,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.464,3232.566,TRUE,2.9998789e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373412.58006,groupby,G1_1e7_1e2_0_0,10000000,sum v1 by id1,100,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.419,3363.48,TRUE,2.9998789e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373413.53860,groupby,G1_1e7_1e2_0_0,10000000,sum v1 by id1:id2,10000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.952,3426.348,TRUE,2.9998789e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373414.47214,groupby,G1_1e7_1e2_0_0,10000000,sum v1 by id1:id2,10000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.927,3485.516,TRUE,2.9998789e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373415.39684,groupby,G1_1e7_1e2_0_0,10000000,sum v1 mean v3 by id3,100000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.918,3691.379,TRUE,2.9998789e7;4999719.622,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373416.29214,groupby,G1_1e7_1e2_0_0,10000000,sum v1 mean v3 by id3,100000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.888,3693.445,TRUE,2.9998789e7;4999719.622,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373416.62328,groupby,G1_1e7_1e2_0_0,10000000,mean v1:v3 by id4,100,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.324,3693.449,TRUE,299.988;799.894;4999.767,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373416.96887,groupby,G1_1e7_1e2_0_0,10000000,mean v1:v3 by id4,100,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.339,3706.402,TRUE,299.988;799.894;4999.767,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373417.36656,groupby,G1_1e7_1e2_0_0,10000000,sum v1:v3 by id6,100000,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.391,3707.195,TRUE,2.9998789e7;7.998936e7;4.99976651408e8,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373417.76884,groupby,G1_1e7_1e2_0_0,10000000,sum v1:v3 by id6,100000,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.395,3708.457,TRUE,2.9998789e7;7.998936e7;4.99976651408e8,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373418.75067,groupby,G1_1e7_1e2_0_0,10000000,median v3 sd v3 by id4 id5,10000,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.975,3708.883,TRUE,499920.14;288648.108,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373419.71927,groupby,G1_1e7_1e2_0_0,10000000,median v3 sd v3 by id4 id5,10000,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.962,3735.328,TRUE,499920.14;288648.108,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373420.50556,groupby,G1_1e7_1e2_0_0,10000000,max v1 - min v2 by id3,100000,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.779,3749.93,TRUE,399882.0,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373421.28703,groupby,G1_1e7_1e2_0_0,10000000,max v1 - min v2 by id3,100000,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.775,3755.906,TRUE,399882.0,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373421.79094,groupby,G1_1e7_1e2_0_0,10000000,largest two v3 by id6,100000,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.497,3755.91,TRUE,1.9700450588e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373422.29218,groupby,G1_1e7_1e2_0_0,10000000,largest two v3 by id6,100000,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.494,3757.898,TRUE,1.9700450588e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373426.93950,groupby,G1_1e7_1e2_0_0,10000000,regression v1 v2 by id2 id4,10000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,4.64,3779.898,TRUE,9.839,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373427.96163,groupby,G1_1e7_1e2_0_0,10000000,regression v1 v2 by id2 id4,10000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,1.015,3777.848,TRUE,9.839,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373431.45879,groupby,G1_1e7_1e2_0_0,10000000,sum v3 count by id1:id6,10000000,8,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,3.47,3780.461,TRUE,4.99976651408e8;1.0e7,2.1e-2,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373434.93595,groupby,G1_1e7_1e2_0_0,10000000,sum v3 count by id1:id6,10000000,8,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,3.45,3780.465,TRUE,4.99976651408e8;1.0e7,2.0e-2,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373454.13086,groupby,G1_1e7_1e1_0_0,10000000,sum v1 by id1,10,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.782,3360.973,TRUE,2.9998597e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373454.84146,groupby,G1_1e7_1e1_0_0,10000000,sum v1 by id1,10,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.704,3617.637,TRUE,2.9998597e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373455.46884,groupby,G1_1e7_1e1_0_0,10000000,sum v1 by id1:id2,100,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.621,3805.562,TRUE,2.9998597e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373456.06616,groupby,G1_1e7_1e1_0_0,10000000,sum v1 by id1:id2,100,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.591,3958.305,TRUE,2.9998597e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373457.04983,groupby,G1_1e7_1e1_0_0,10000000,sum v1 mean v3 by id3,999951,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.975,4035.711,TRUE,2.9998597e7;5.0000558524e7,2.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373458.02321,groupby,G1_1e7_1e1_0_0,10000000,sum v1 mean v3 by id3,999951,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.964,4048.434,TRUE,2.9998597e7;5.0000558524e7,2.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373458.52023,groupby,G1_1e7_1e1_0_0,10000000,mean v1:v3 by id4,10,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.49,4049.125,TRUE,29.999;79.992;499.981,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373459.03650,groupby,G1_1e7_1e1_0_0,10000000,mean v1:v3 by id4,10,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.509,4049.129,TRUE,29.999;79.992;499.981,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373459.73696,groupby,G1_1e7_1e1_0_0,10000000,sum v1:v3 by id6,999965,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.691,4049.133,TRUE,2.9998597e7;7.9991898e7;4.9998074701e8,3.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373460.45297,groupby,G1_1e7_1e1_0_0,10000000,sum v1:v3 by id6,999965,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.706,4051.227,TRUE,2.9998597e7;7.9991898e7;4.9998074701e8,3.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373461.23564,groupby,G1_1e7_1e1_0_0,10000000,median v3 sd v3 by id4 id5,100,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.776,4052.824,TRUE,4999.573;2887.162,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373462.03573,groupby,G1_1e7_1e1_0_0,10000000,median v3 sd v3 by id4 id5,100,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.793,4099.203,TRUE,4999.573;2887.162,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373462.88644,groupby,G1_1e7_1e1_0_0,10000000,max v1 - min v2 by id3,999951,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.843,4099.895,TRUE,2789316.0,1.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373463.76247,groupby,G1_1e7_1e1_0_0,10000000,max v1 - min v2 by id3,999951,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.868,4102.008,TRUE,2789316.0,1.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373464.57348,groupby,G1_1e7_1e1_0_0,10000000,largest two v3 by id6,999965,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.803,4102.012,TRUE,1.70016562642e8,1.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373465.36583,groupby,G1_1e7_1e1_0_0,10000000,largest two v3 by id6,999965,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.784,4102.016,TRUE,1.70016562642e8,1.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373469.80990,groupby,G1_1e7_1e1_0_0,10000000,regression v1 v2 by id2 id4,100,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,4.437,4102.094,TRUE,1.0e-3,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373470.61159,groupby,G1_1e7_1e1_0_0,10000000,regression v1 v2 by id2 id4,100,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.795,4157.262,TRUE,1.0e-3,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373474.02357,groupby,G1_1e7_1e1_0_0,10000000,sum v3 count by id1:id6,10000000,8,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,3.385,4174.926,TRUE,4.9998074701e8;1.0e7,2.0e-2,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373477.46556,groupby,G1_1e7_1e1_0_0,10000000,sum v3 count by id1:id6,10000000,8,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,3.414,4174.242,TRUE,4.9998074701e8;1.0e7,2.1e-2,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373496.80616,groupby,G1_1e7_2e0_0_0,10000000,sum v1 by id1,2,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.829,3343.145,TRUE,3.0000054e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373497.53523,groupby,G1_1e7_2e0_0_0,10000000,sum v1 by id1,2,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.722,3477.602,TRUE,3.0000054e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373498.28654,groupby,G1_1e7_2e0_0_0,10000000,sum v1 by id1:id2,4,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.745,3572.992,TRUE,3.0000054e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373498.99026,groupby,G1_1e7_2e0_0_0,10000000,sum v1 by id1:id2,4,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.697,3574.438,TRUE,3.0000054e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373500.09273,groupby,G1_1e7_2e0_0_0,10000000,sum v1 mean v3 by id3,4323566,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,1.087,3766.984,TRUE,3.0000054e7;2.16107547389e8,9.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373501.18427,groupby,G1_1e7_2e0_0_0,10000000,sum v1 mean v3 by id3,4323566,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,1.076,3861.641,TRUE,3.0000054e7;2.16107547389e8,9.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373501.79272,groupby,G1_1e7_2e0_0_0,10000000,mean v1:v3 by id4,2,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.602,3861.645,TRUE,6.0;15.997;99.987,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373502.39691,groupby,G1_1e7_2e0_0_0,10000000,mean v1:v3 by id4,2,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.598,3861.648,TRUE,6.0;15.997;99.987,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373503.63254,groupby,G1_1e7_2e0_0_0,10000000,sum v1:v3 by id6,4322014,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,1.217,3864.609,TRUE,3.0000054e7;7.9986418e7;4.99936032106e8,1.2e-2,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373504.86611,groupby,G1_1e7_2e0_0_0,10000000,sum v1:v3 by id6,4322014,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,1.215,3866.086,TRUE,3.0000054e7;7.9986418e7;4.99936032106e8,1.2e-2,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373506.48018,groupby,G1_1e7_2e0_0_0,10000000,median v3 sd v3 by id4 id5,4,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,1.607,3866.172,TRUE,199.97;115.489,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373508.20618,groupby,G1_1e7_2e0_0_0,10000000,median v3 sd v3 by id4 id5,4,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,1.719,4115.758,TRUE,199.97;115.489,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373509.17398,groupby,G1_1e7_2e0_0_0,10000000,max v1 - min v2 by id3,4323566,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.958,4117.789,TRUE,-8263086.0,3.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373510.13381,groupby,G1_1e7_2e0_0_0,10000000,max v1 - min v2 by id3,4323566,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.95,4119.465,TRUE,-8263086.0,3.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373511.46860,groupby,G1_1e7_2e0_0_0,10000000,largest two v3 by id6,4322014,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,1.323,4120.156,TRUE,4.19079607e8,6.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373512.78868,groupby,G1_1e7_2e0_0_0,10000000,largest two v3 by id6,4322014,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,1.308,4120.758,TRUE,4.19079607e8,6.0e-3,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373518.15160,groupby,G1_1e7_2e0_0_0,10000000,regression v1 v2 by id2 id4,4,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,5.356,3990.84,TRUE,0.0,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373520.06378,groupby,G1_1e7_2e0_0_0,10000000,regression v1 v2 by id2 id4,4,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,1.906,3921.012,TRUE,0.0,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373523.61373,groupby,G1_1e7_2e0_0_0,10000000,sum v3 count by id1:id6,10000000,8,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,3.523,4183.754,TRUE,4.99936032106e8;1.0e7,2.1e-2,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373527.17079,groupby,G1_1e7_2e0_0_0,10000000,sum v3 count by id1:id6,10000000,8,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,3.529,4317.875,TRUE,4.99936032106e8;1.0e7,2.1e-2,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373545.92806,groupby,G1_1e7_1e2_0_1,10000000,sum v1 by id1,100,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.426,3208.434,TRUE,2.9998789e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373546.30335,groupby,G1_1e7_1e2_0_1,10000000,sum v1 by id1,100,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.369,3324.195,TRUE,2.9998789e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373546.80814,groupby,G1_1e7_1e2_0_1,10000000,sum v1 by id1:id2,10000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.498,3540.422,TRUE,2.9998789e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373547.23135,groupby,G1_1e7_1e2_0_1,10000000,sum v1 by id1:id2,10000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.417,3601.004,TRUE,2.9998789e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373548.06628,groupby,G1_1e7_1e2_0_1,10000000,sum v1 mean v3 by id3,100000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.828,3609.461,TRUE,2.9998789e7;4999719.622,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373548.90434,groupby,G1_1e7_1e2_0_1,10000000,sum v1 mean v3 by id3,100000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.831,3611.812,TRUE,2.9998789e7;4999719.622,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373549.23765,groupby,G1_1e7_1e2_0_1,10000000,mean v1:v3 by id4,100,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.327,3611.816,TRUE,299.988;799.894;4999.767,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373549.58458,groupby,G1_1e7_1e2_0_1,10000000,mean v1:v3 by id4,100,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.34,3611.82,TRUE,299.988;799.894;4999.767,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373550.00177,groupby,G1_1e7_1e2_0_1,10000000,sum v1:v3 by id6,100000,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.41,3612.078,TRUE,2.9998789e7;7.998936e7;4.99976651408e8,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373550.41794,groupby,G1_1e7_1e2_0_1,10000000,sum v1:v3 by id6,100000,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.409,3612.16,TRUE,2.9998789e7;7.998936e7;4.99976651408e8,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373551.40619,groupby,G1_1e7_1e2_0_1,10000000,median v3 sd v3 by id4 id5,10000,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.982,3632.285,TRUE,499920.14;288648.108,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373552.37425,groupby,G1_1e7_1e2_0_1,10000000,median v3 sd v3 by id4 id5,10000,4,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.961,3632.348,TRUE,499920.14;288648.108,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373553.10543,groupby,G1_1e7_1e2_0_1,10000000,max v1 - min v2 by id3,100000,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.724,3632.648,TRUE,399882.0,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373553.83360,groupby,G1_1e7_1e2_0_1,10000000,max v1 - min v2 by id3,100000,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.722,3639.508,TRUE,399882.0,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373554.33773,groupby,G1_1e7_1e2_0_1,10000000,largest two v3 by id6,100000,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,0.497,3639.512,TRUE,1.9700450588e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373554.84381,groupby,G1_1e7_1e2_0_1,10000000,largest two v3 by id6,100000,2,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.499,3653.461,TRUE,1.9700450588e7,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373559.28003,groupby,G1_1e7_1e2_0_1,10000000,regression v1 v2 by id2 id4,10000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,4.43,3661.594,TRUE,9.839,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373560.04359,groupby,G1_1e7_1e2_0_1,10000000,regression v1 v2 by id2 id4,10000,3,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,0.757,3697.168,TRUE,9.839,0.0,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373563.51724,groupby,G1_1e7_1e2_0_1,10000000,sum v3 count by id1:id6,10000000,8,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,1,3.446,3683.109,TRUE,4.99976651408e8;1.0e7,2.0e-2,,FALSE,c6id.4xlarge -ip-172-31-18-189,1786373405,1786373566.95592,groupby,G1_1e7_1e2_0_1,10000000,sum v3 count by id1:id6,10000000,8,haskell,2.2.0.0,dataframe-2.2.0.0,groupBy,2,3.411,3696.004,TRUE,4.99976651408e8;1.0e7,2.1e-2,,FALSE,c6id.4xlarge From 562223e4516436be3119701972243dacf83e2bea Mon Sep 17 00:00:00 2001 From: Michael Chavinda Date: Sat, 29 Aug 2026 15:02:48 -0600 Subject: [PATCH 7/8] fix: Updated implementation checked on AWS box --- _benchplot/benchplot-dict.R | 2 +- haskell/VERSION | 2 +- haskell/groupby-haskell.hs | 42 ++++++++++++++------------------- haskell/haskell-benchmark.cabal | 4 ++-- haskell/join-haskell.hs | 15 +++--------- haskell/ver-haskell.sh | 2 +- 6 files changed, 26 insertions(+), 41 deletions(-) diff --git a/_benchplot/benchplot-dict.R b/_benchplot/benchplot-dict.R index 051238a3..89d111da 100644 --- a/_benchplot/benchplot-dict.R +++ b/_benchplot/benchplot-dict.R @@ -369,7 +369,7 @@ groupby.data.exceptions = {list( "chdb" = {list( )}, "haskell" = {list( - "Not tested/OOM" = c("G1_1e8_1e2_0_0", "G1_1e8_1e1_0_0", "G1_1e8_2e0_0_0", "G1_1e8_1e2_0_1", "G1_1e8_1e2_5_0", "G1_1e9_1e2_0_0","G1_1e9_1e2_0_1","G1_1e9_1e2_5_0","G1_1e9_1e1_0_0", "G1_1e9_2e0_0_0") + "Not tested/OOM" = c("G1_1e8_1e2_0_1", "G1_1e8_1e2_5_0", "G1_1e9_1e2_0_0","G1_1e9_1e2_0_1","G1_1e9_1e2_5_0","G1_1e9_1e1_0_0", "G1_1e9_2e0_0_0") )} )} groupby.exceptions = task.exceptions(groupby.query.exceptions, groupby.data.exceptions) diff --git a/haskell/VERSION b/haskell/VERSION index ec9d2348..cf159232 100644 --- a/haskell/VERSION +++ b/haskell/VERSION @@ -1 +1 @@ -3.5.0.0 \ No newline at end of file +3.6.0.0 \ No newline at end of file diff --git a/haskell/groupby-haskell.hs b/haskell/groupby-haskell.hs index 208d772c..f14b9bc5 100755 --- a/haskell/groupby-haskell.hs +++ b/haskell/groupby-haskell.hs @@ -5,13 +5,10 @@ import BenchmarkCommon import Control.Arrow ((>>>)) import Control.Monad (forM_) -import Debug.Trace (traceMarkerIO) import Data.Functor ((<&>)) -import qualified Data.List as L import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T -import qualified Data.Vector as V import qualified Data.Vector.Unboxed as VU import DataFrame.Functions ((.=)) import qualified DataFrame.Functions as F @@ -22,13 +19,14 @@ import DataFrame.IO.CSV ( ) import DataFrame.IO.CSV.Fast import DataFrame.Internal.DataFrame (DataFrame) -import DataFrame.Internal.Expression (AggStrategy (..), Expr (..)) -import DataFrame.Schema (schemaType) +import DataFrame.Internal.Expression (Expr (..)) import qualified DataFrame.Operations.Aggregation as D import qualified DataFrame.Operations.Core as D -import DataFrame.Operations.Merge () -- Semigroup DataFrame (row concat) +-- Semigroup DataFrame (row concat) +import DataFrame.Operations.Merge () import qualified DataFrame.Operations.Subset as D import qualified DataFrame.Operations.Transformations as D +import DataFrame.Schema (schemaType) import System.Environment (getEnv, lookupEnv) import System.IO (BufferMode (..), hPutStrLn, hSetBuffering, stderr, stdout) @@ -113,7 +111,7 @@ runBenchmark srcFile dataName machineType = do config df "sum v1 by id1" - (D.groupBy [F.name id1] >>> D.aggregate [F.sum v1 `F.as` "v1_sum"]) + (D.groupBy [F.name id1] >>> D.aggregate ["v1_sum" .= F.sum v1]) (\res -> [chkSumInt "v1_sum" res]) -- Q2: Sum v1 by id1:id2 @@ -178,14 +176,19 @@ runBenchmark srcFile dataName machineType = do -- Q8: largest two v3 by id6, exploded to two rows per group -- (polars reference shape: group_by(id6).agg(v3.top_k(2)).explode(v3)). + -- 'F.topK' returns the pair in descending order; the library has no explode, + -- so we take the two ends and stack the halves. runQuestion config df "largest two v3 by id6" ( \d -> - let r = + let top2 = F.col @[Double] "top2" + r = ( D.groupBy [F.name id6] - >>> D.aggregate ["v3" .= F.maximum v3, "v3b" .= top2Snd v3] + >>> D.aggregate ["top2" .= F.topK 2 v3] + >>> D.derive "v3" (F.fromMaybe nan (F.firstOrNothing top2)) + >>> D.derive "v3b" (F.fromMaybe nan (F.lastOrNothing top2)) ) d in D.select ["id6", "v3"] r @@ -235,14 +238,12 @@ runQuestion :: IO () runQuestion cfg inputDF qLabel transform chkFn = do forM_ [1, 2] $ \runNum -> do - traceMarkerIO (qLabel ++ " run" ++ show runNum ++ " START") (resultDF, calcTime) <- timeIt $ do let result = freshRun runNum transform inputDF print result return result memUsage <- getMemoryUsage let (outRows, outCols) = D.dimensions resultDF - traceMarkerIO (qLabel ++ " run" ++ show runNum ++ " END") (chkValues, chkTime) <- timeIt $ do let vals = chkFn resultDF print vals @@ -250,6 +251,12 @@ runQuestion cfg inputDF qLabel transform chkFn = do writeLog cfg qLabel outRows outCols runNum calcTime memUsage chkValues chkTime putStrLn $ qLabel ++ " completed." +{- | Stand-in for a group with fewer than two values. The benchmark data has no +size-1 id6 groups, so this never reaches the checksum. +-} +nan :: Double +nan = 0 / 0 + chkSumInt :: String -> DataFrame -> Double chkSumInt col df = case D.columnAsIntVector (F.col @Int (T.pack col)) df of @@ -262,19 +269,6 @@ chkSumDbl col df = Right vec -> VU.sum vec Left _ -> 0.0 -{- | Per-group second-largest value (with 'F.maximum' this yields polars Q8's -top_k(2) pair). The vectorized kernel path returns NaN for a group of size 1 -(the benchmark data has no size-1 id6 groups); this interpreter fallback -mirrors that. --} -top2Snd :: Expr Double -> Expr Double -top2Snd = Agg (CollectAgg "top2Snd" f) - where - f :: V.Vector Double -> Double - f v = case L.sortBy (flip compare) (V.toList v) of - (_ : b : _) -> b - _ -> 0 / 0 - {- | Squared Pearson correlation of v1,v2 from per-group moment sums (polars Q9). Degenerate groups (zero variance) contribute 0 rather than poisoning the sum with NaN; the 1e7 data has no such groups so the checksum is unaffected. diff --git a/haskell/haskell-benchmark.cabal b/haskell/haskell-benchmark.cabal index 37e85766..786fae4b 100644 --- a/haskell/haskell-benchmark.cabal +++ b/haskell/haskell-benchmark.cabal @@ -7,10 +7,10 @@ common df-deps build-depends: base >= 4.7 && < 5 , bytestring >= 0.11 && < 0.13 , cassava >= 0.1 && < 1 - , dataframe-core ^>= 2.4 + , dataframe-core ^>= 2.5 , dataframe-parsing ^>= 2.2 , dataframe-csv ^>= 2.3 - , dataframe-operations ^>= 2.4 + , dataframe-operations ^>= 2.5 , dataframe-fastcsv ^>= 1.4.1 , text >= 2.1 , vector >= 0.12 diff --git a/haskell/join-haskell.hs b/haskell/join-haskell.hs index d00bf9d8..773ceca7 100644 --- a/haskell/join-haskell.hs +++ b/haskell/join-haskell.hs @@ -19,11 +19,10 @@ import DataFrame.IO.CSV ( defaultReadOptions, ) import DataFrame.IO.CSV.Fast (fastReadCsvWithOpts) -import DataFrame.Internal.Column (forceColumn) -import DataFrame.Internal.DataFrame (DataFrame, columnNames, getColumn) -import DataFrame.Schema (SchemaType, schemaType) +import DataFrame.Internal.DataFrame (DataFrame, forceDataFrame) import qualified DataFrame.Operations.Core as D import qualified DataFrame.Operations.Join as DJ +import DataFrame.Schema (SchemaType, schemaType) import Numeric (showEFloat) import System.Environment (getEnv, lookupEnv) import System.IO (BufferMode (..), hSetBuffering, stdout) @@ -152,9 +151,7 @@ runJoin cfg leftDF rightDF qLabel joinFn = do forM_ [1, 2] $ \runNum -> do performGC (resultDF, calcTime) <- timeIt $ do - let res = freshRun runNum (uncurry joinFn) (leftDF, rightDF) - forceAllColumns res - return res + evaluate (forceDataFrame (freshRun runNum (uncurry joinFn) (leftDF, rightDF))) memUsage <- getMemoryUsage let (outRows, outCols) = D.dimensions resultDF @@ -170,12 +167,6 @@ runJoin cfg leftDF rightDF qLabel joinFn = do putStrLn $ qLabel ++ " completed." -forceAllColumns :: DataFrame -> IO () -forceAllColumns df = forM_ (columnNames df) $ \nm -> - case getColumn nm df of - Just c -> evaluate (forceColumn c) >> pure () - Nothing -> pure () - {- | Sum a Double column, skipping nulls/NaN. A left join surfaces the right-side value column as @Maybe Double@; 'columnAsDoubleVector' coerces nulls to NaN, so we drop NaN (matching polars sum, which skips nulls). diff --git a/haskell/ver-haskell.sh b/haskell/ver-haskell.sh index cdefc906..335c5fff 100755 --- a/haskell/ver-haskell.sh +++ b/haskell/ver-haskell.sh @@ -3,7 +3,7 @@ set -e cd haskell -export HASKELL_DF_VERSION="3.5.0.0" +export HASKELL_DF_VERSION="3.6.0.0" echo "${HASKELL_DF_VERSION}" > VERSION From d0f1c574b58a8d8b292c44412db064254e94d6d8 Mon Sep 17 00:00:00 2001 From: Michael Chavinda Date: Wed, 9 Sep 2026 21:49:20 -0700 Subject: [PATCH 8/8] Expand Haskell implementation to more non-null --- _benchplot/benchplot-dict.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_benchplot/benchplot-dict.R b/_benchplot/benchplot-dict.R index 89d111da..de1637b8 100644 --- a/_benchplot/benchplot-dict.R +++ b/_benchplot/benchplot-dict.R @@ -369,7 +369,7 @@ groupby.data.exceptions = {list( "chdb" = {list( )}, "haskell" = {list( - "Not tested/OOM" = c("G1_1e8_1e2_0_1", "G1_1e8_1e2_5_0", "G1_1e9_1e2_0_0","G1_1e9_1e2_0_1","G1_1e9_1e2_5_0","G1_1e9_1e1_0_0", "G1_1e9_2e0_0_0") + "Not tested/OOM" = c("G1_1e8_1e2_5_0", "G1_1e9_1e2_0_0","G1_1e9_1e2_0_1","G1_1e9_1e2_5_0","G1_1e9_1e1_0_0", "G1_1e9_2e0_0_0") )} )} groupby.exceptions = task.exceptions(groupby.query.exceptions, groupby.data.exceptions)