그래서, 얼마나 빨리 우리가 할 수있는의는 @ JA에 의해 생성 된 파일을 몇 가지 검사를하자의 코드 :
cat data.txt > /dev/null
--> 0.17 seconds
하스켈에서 동일합니까?
import qualified Data.ByteString as B
f = id
main = B.readFile "data.txt" >>= return . f >>= B.putStr
타이밍?
time ./Test > /dev/null
--> 0.32 seconds
두 배의 시간이 걸리지 만 나쁘지는 않습니다. 때문에 엄격한 bytestring을 사용하여 우리는 그것을 1 초 안에 청크하고 싶습니다.
다음으로 Vector
을 사용할 수 있습니까? 아니면 너무 느린가요? Vector
개의 덩어리를 만들어 다시 다시 붙이자. 최적화 된 출력을 위해 blaze-builder
패키지를 사용합니다.
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Data.Vector as V
import qualified Blaze.ByteString.Builder as BB
import Data.Monoid
recordLen = 36
lineEndingLen = 2 -- Windows! change to 1 for Unix
numRecords = (`div` (recordLen + lineEndingLen)) . B.length
substr idx len = B.take len . B.drop idx
recordByIdx idx = substr (idx*(recordLen+lineEndingLen)) recordLen
mkVector :: B.ByteString -> V.Vector (B.ByteString)
mkVector bs = V.generate (numRecords bs) (\i -> recordByIdx i bs)
mkBS :: V.Vector (B.ByteString) -> L.ByteString
mkBS = BB.toLazyByteString . V.foldr foldToBS mempty
where foldToBS :: B.ByteString -> BB.Builder -> BB.Builder
foldToBS = mappend . BB.fromWrite . BB.writeByteString
main = B.readFile "data.txt" >>= return . mkBS . mkVector >>= L.putStr
얼마나 걸리나요?
time ./Test2 > /dev/null
--> 1.06 seconds
전혀 나쁘지 않습니다! 고정 된 청크 위치 대신에 라인을 읽는 정규 표현식을 사용하더라도, 우리는 여전히 심각한 성능 히트없이 Vector
에 청크를 넣을 수 있다고 결론을 내릴 수 있습니다.
남은 항목은 무엇입니까? 정렬. 이론적으로 버킷 정렬은 이러한 종류의 문제에 대한 이상적인 알고리즘이어야합니다. 나는 하나를 자신을 구현하는 시도 :
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Data.Vector as V
import qualified Data.Vector.Generic.Mutable as MV
import qualified Blaze.ByteString.Builder as BB
import Data.Monoid
import Control.Monad.ST
import Control.Monad.Primitive
recordLen = 36
lineEndingLen = 2 -- Windows! change to 1 for Unix
numRecords = (`div` (recordLen + lineEndingLen)) . B.length
substr idx len = B.take len . B.drop idx
recordByIdx idx = substr (idx*(recordLen+lineEndingLen)) (recordLen+lineEndingLen)
mkVector :: B.ByteString -> V.Vector (B.ByteString)
mkVector bs = V.generate (numRecords bs) (\i -> recordByIdx i bs)
mkBS :: V.Vector (B.ByteString) -> L.ByteString
mkBS = BB.toLazyByteString . V.foldr foldToBS mempty
where foldToBS :: B.ByteString -> BB.Builder -> BB.Builder
foldToBS = mappend . BB.fromWrite . BB.writeByteString
bucketSort :: Int -> V.Vector B.ByteString -> V.Vector B.ByteString
bucketSort chunkSize v = runST $ emptyBuckets >>= \bs ->
go v bs lastIdx (chunkSize - 1)
where lastIdx = V.length v - 1
emptyBuckets :: ST s (V.MVector (PrimState (ST s)) [B.ByteString])
emptyBuckets = V.thaw $ V.generate 256 (const [])
go :: V.Vector B.ByteString ->
V.MVector (PrimState (ST s)) [B.ByteString] ->
Int -> Int -> ST s (V.Vector B.ByteString)
go v _ _ (-1) = return v
go _ buckets (-1) testIdx = do
v' <- unbucket buckets
bs <- emptyBuckets
go v' bs lastIdx (testIdx - 1)
go v buckets idx testIdx = do
let testChunk = v V.! idx
testByte = fromIntegral $ testChunk `B.index` testIdx
b <- MV.read buckets testByte
MV.write buckets testByte (testChunk:b)
go v buckets (idx-1) testIdx
unbucket :: V.MVector (PrimState (ST s)) [B.ByteString] ->
ST s (V.Vector B.ByteString)
unbucket v = do
v' <- V.freeze v
return . V.fromList . concat . V.toList $ v'
main = B.readFile "data.txt" >>= return . process >>= L.putStr
where process = mkBS .
bucketSort (recordLen) .
mkVector
테스트는 아마 허용에 대한 1시 50분 분의 시간을 주었다. 우리는 몇 백만의 범위에서 n을위한 O (c * n) 알고리즘과 36 * 무언가의 상수 c를 이야기하고 있습니다. 그러나 당신이 그것을 더 이상 최적화 할 수 있다고 확신합니다.
vector-algorithms
패키지를 사용해도됩니다. 힙 정렬로 테스트하면 :
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Data.Vector as V
import qualified Blaze.ByteString.Builder as BB
import Data.Vector.Algorithms.Heap (sort)
import Data.Monoid
import Control.Monad.ST
recordLen = 36
lineEndingLen = 2 -- Windows! change to 1 for Unix
numRecords = (`div` (recordLen + lineEndingLen)) . B.length
substr idx len = B.take len . B.drop idx
recordByIdx idx = substr (idx*(recordLen+lineEndingLen)) (recordLen+lineEndingLen)
mkVector :: B.ByteString -> V.Vector (B.ByteString)
mkVector bs = V.generate (numRecords bs) (\i -> recordByIdx i bs)
mkBS :: V.Vector (B.ByteString) -> L.ByteString
mkBS = BB.toLazyByteString . V.foldr foldToBS mempty
where foldToBS :: B.ByteString -> BB.Builder -> BB.Builder
foldToBS = mappend . BB.fromWrite . BB.writeByteString
sortIt v = runST $ do
mv <- V.thaw v
sort mv
V.freeze mv
main = B.readFile "data.txt" >>= return . process >>= L.putStr
where process = mkBS .
sortIt .
mkVector
이 내 컴퓨터에 대한 1시 20분 분의 일을한다, 그래서 지금 나의 버킷 정렬보다 더 빠르다. 두 가지 최종 솔루션 모두 1 ~ 2GB의 RAM 범위에서 무언가를 소비합니다.
충분하니?
필자는 bytestring-trie를 사용하여 시작했지만, 문제가 발생하기까지는 매우 오랜 시간이 걸렸습니다. 해시 맵 솔루션을 노크 해보고 그 방법을 볼 수 있습니다. 두 번째 부분은 hGetLine을 사용하여 한 번에 한 줄씩 읽습니다. 그런 다음 정규 표현식 패키지에서'match'를 사용하여 행에서 여러 하위 문자열을 추출합니다. –
해시 맵 버전은 GC 축적에도 좋지 않은 것 같습니다. 각 스레드가 100k 회선을 완료 한 후 "전체 사용자의 생산성 32.0 %, 전체 경과의 198.0 %"이고, 200k 이후에는 "생산성 11.4 %, 총 경과 시간 132.1 %"입니다. 대조적으로, (GC를 일으키는 구문 분석이 아닌지 확인하기 위해) 구문 분석 만하면 200k 회선 이후 "전체 사용자의 생산성 70.2 %, 전체 경과의 217.9 %"입니다. –