Плохой анализ производительности двоичного файла в haskell
У меня есть набор бинарных записей, упакованных в файл, и я читаю их с помощью Data.ByteString.Lazy и Data.Binary.Get. С моей текущей реализацией файл 8 Мб занимает 6 секунд для разбора.
import qualified Data.ByteString.Lazy as BL
import Data.Binary.Get
data Trade = Trade { timestamp :: Int, price :: Int , qty :: Int } deriving (Show)
getTrades = do
empty <- isEmpty
if empty
then return []
else do
timestamp <- getWord32le
price <- getWord32le
qty <- getWord16le
rest <- getTrades
let trade = Trade (fromIntegral timestamp) (fromIntegral price) (fromIntegral qty)
return (trade : rest)
main :: IO()
main = do
input <- BL.readFile "trades.bin"
let trades = runGet getTrades input
print $ length trades
Что я могу сделать, чтобы сделать это быстрее?
Ответы
Ответ 1
Ваш код декодирует 8 МБ файл менее чем за одну секунду (ghc-7.4.1) - конечно, я скомпилирован с -O2
.
Однако для этого потребовалось чрезмерное количество пространства стека. Вы можете уменьшить
- время
- пространство стека
- пустое пространство
требуемый путем добавления большей строгости в соответствующих местах и использования аккумулятора для сбора разобранных сделок.
{-# LANGUAGE BangPatterns #-}
module Main (main) where
import qualified Data.ByteString.Lazy as BL
import Data.Binary.Get
data Trade = Trade { timestamp :: {-# UNPACK #-} !Int
, price :: {-# UNPACK #-} !Int
, qty :: {-# UNPACK #-} !Int
} deriving (Show)
getTrades :: Get [Trade]
getTrades = go []
where
go !acc = do
empty <- isEmpty
if empty
then return $! reverse acc
else do
!timestamp <- getWord32le
!price <- getWord32le
!qty <- getWord16le
let !trade = Trade (fromIntegral timestamp) (fromIntegral price) (fromIntegral qty)
go (trade : acc)
main :: IO()
main = do
input <- BL.readFile "trades.bin"
let trades = runGet getTrades input
print $ length trades
Строгость и распаковка гарантируют, что никакая работа не будет отменена, чтобы вернуться, чтобы укусить вас позже, указав часть ByteString
, которая уже должна была быть забыта.
Если вам нужно Trade
иметь ленивые поля, вы все равно можете декодировать через тип со строгими полями и map
преобразование по списку результатов, чтобы извлечь выгоду из более строгого декодирования.
Однако код все еще тратит много времени на сбор мусора, поэтому дальнейшие улучшения могут по-прежнему быть необходимы.
Ответ 2
Рефакторинг его немного (в основном с левым краем) дает намного лучшую производительность и снижает накладные расходы GC, немного разбирающий файл размером 8388600.
{-# LANGUAGE BangPatterns #-}
module Main (main) where
import qualified Data.ByteString.Lazy as BL
import Data.Binary.Get
data Trade = Trade
{ timestamp :: {-# UNPACK #-} !Int
, price :: {-# UNPACK #-} !Int
, qty :: {-# UNPACK #-} !Int
} deriving (Show)
getTrade :: Get Trade
getTrade = do
timestamp <- getWord32le
price <- getWord32le
qty <- getWord16le
return $! Trade (fromIntegral timestamp) (fromIntegral price) (fromIntegral qty)
countTrades :: BL.ByteString -> Int
countTrades input = stepper (0, input) where
stepper (!count, !buffer)
| BL.null buffer = count
| otherwise =
let (trade, rest, _) = runGetState getTrade buffer 0
in stepper (count+1, rest)
main :: IO()
main = do
input <- BL.readFile "trades.bin"
let trades = countTrades input
print trades
И соответствующая статистика времени выполнения. Несмотря на то, что номера распределения близки, размер GC и максимальный размер кучи немного отличаются между версиями.
Все примеры здесь были построены с помощью GHC 7.4.1-O2.
Исходный источник, запускаемый с + RTS -K1G -RTS из-за чрезмерного использования пространства стека:
426,003,680 bytes allocated in the heap
443,141,672 bytes copied during GC
99,305,920 bytes maximum residency (9 sample(s))
203 MB total memory in use (0 MB lost due to fragmentation)
Total time 0.62s ( 0.81s elapsed)
%GC time 83.3% (86.4% elapsed)
Редакция Daniel:
357,851,536 bytes allocated in the heap
220,009,088 bytes copied during GC
40,846,168 bytes maximum residency (8 sample(s))
85 MB total memory in use (0 MB lost due to fragmentation)
Total time 0.24s ( 0.28s elapsed)
%GC time 69.1% (71.4% elapsed)
И этот пост:
290,725,952 bytes allocated in the heap
109,592 bytes copied during GC
78,704 bytes maximum residency (10 sample(s))
2 MB total memory in use (0 MB lost due to fragmentation)
Total time 0.06s ( 0.07s elapsed)
%GC time 5.0% (6.0% elapsed)