우리는 "메시지"를 수신 및 전달하는 프로그램을 개발하고 있으며, 해당 메시지의 임시 기록을 유지하면서 요청시 메시지 기록을 알려줄 수 있습니다. 메시지는 숫자로 식별되며 일반적으로 크기는 약 1KB이므로 수십만 개의 메시지를 보관해야합니다.
대기 시간을 위해이 프로그램을 최적화하려고합니다. 메시지를주고받는 시간은 10 밀리 초 미만이어야합니다.
이 프로그램은 Haskell로 작성되었으며 GHC로 컴파일되었습니다. 그러나 실제 프로그램에서 100 밀리 초가 넘는 지연 시간 요구 사항에 대해서는 가비지 수집 일시 중지가 너무 길다는 것을 알았습니다.
다음 프로그램은 응용 프로그램의 단순화 된 버전입니다. 를 사용하여 Data.Map.Strict
메시지를 저장합니다. 메시지는로 ByteString
식별됩니다 Int
. 1,000,000 개의 메시지가 숫자 순서대로 삽입되고 기록이 최대 200,000 개의 메시지로 유지되도록 가장 오래된 메시지가 계속 제거됩니다.
module Main (main) where
import qualified Control.Exception as Exception
import qualified Control.Monad as Monad
import qualified Data.ByteString as ByteString
import qualified Data.Map.Strict as Map
data Msg = Msg !Int !ByteString.ByteString
type Chan = Map.Map Int ByteString.ByteString
message :: Int -> Msg
message n = Msg n (ByteString.replicate 1024 (fromIntegral n))
pushMsg :: Chan -> Msg -> IO Chan
pushMsg chan (Msg msgId msgContent) =
Exception.evaluate $
let
inserted = Map.insert msgId msgContent chan
in
if 200000 < Map.size inserted
then Map.deleteMin inserted
else inserted
main :: IO ()
main = Monad.foldM_ pushMsg Map.empty (map message [1..1000000])
다음을 사용하여이 프로그램을 컴파일하고 실행했습니다.
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.10.3
$ ghc -O2 -optc-O3 Main.hs
$ ./Main +RTS -s
3,116,460,096 bytes allocated in the heap
385,101,600 bytes copied during GC
235,234,800 bytes maximum residency (14 sample(s))
124,137,808 bytes maximum slop
600 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 6558 colls, 0 par 0.238s 0.280s 0.0000s 0.0012s
Gen 1 14 colls, 0 par 0.179s 0.250s 0.0179s 0.0515s
INIT time 0.000s ( 0.000s elapsed)
MUT time 0.652s ( 0.745s elapsed)
GC time 0.417s ( 0.530s elapsed)
EXIT time 0.010s ( 0.052s elapsed)
Total time 1.079s ( 1.326s elapsed)
%GC time 38.6% (40.0% elapsed)
Alloc rate 4,780,213,353 bytes per MUT second
Productivity 61.4% of total user, 49.9% of total elapsed
여기서 중요한 메트릭은 "최대 일시 중지"0.0515 초 또는 51 밀리 초입니다. 우리는 이것을 최소한 10 배 줄이려고합니다.
실험에 따르면 GC 일시 중지 길이는 기록의 메시지 수에 의해 결정됩니다. 관계는 대략 선형이거나 아마도 초 선형입니다. 다음 표는이 관계를 보여줍니다. ( 여기 에서 벤치마킹 테스트 및 일부 차트를 확인할 수 있습니다 .)
msgs history length max GC pause (ms)
=================== =================
12500 3
25000 6
50000 13
100000 30
200000 56
400000 104
800000 199
1600000 487
3200000 1957
6400000 5378
우리는 이러한 대기 시간을 줄일 수 있는지 여부를 찾기 위해 여러 가지 다른 변수를 실험 해 보았지만 그 중 큰 차이는 없습니다. 이러한 중요하지 않은 변수 중에는 최적화 ( -O
, -O2
); RTS GC 옵션 ( -G
, -H
, -A
, -c
), 코어 (수 -N
), 다른 데이터 구조 ( Data.Sequence
) 메시지의 크기, 생성 단기 쓰레기의 양. 압도적 인 결정 요인은 기록의 메시지 수입니다.
우리의 작업 이론은 각 GC 사이클이 모든 작업 가능한 메모리를 걸어서 복사해야하기 때문에 메시지 수에서 일시 중지가 선형이라는 것입니다. 이는 분명히 선형 작업입니다.
질문 :
- 이 선형 시간 이론이 맞습니까? GC 일시 정지 길이를이 간단한 방식으로 표현할 수 있습니까, 아니면 현실이 더 복잡합니까?
- 작업 메모리에서 GC 일시 정지가 선형이면 관련된 상수 요소를 줄일 수있는 방법이 있습니까?
- 증분 GC 또는 이와 유사한 옵션이 있습니까? 연구 논문 만 볼 수 있습니다. 지연 시간을 줄이기 위해 처리량을 교환 할 의향이 있습니다.
- 여러 프로세스로 분할하는 것 외에 작은 GC주기에 대해 메모리를 "파티션"하는 방법이 있습니까?
COntrol.Concurrent.Chan
하십니까 (예 : 사용하지 않습니까? 가변 객체가 방정식을 변경 함)? 나는 당신이 어떤 쓰레기를 생성하고 가능한 한 적은 양을 만드는지 (예를 들어, 융합이 일어나도록 시도해보십시오 -funbox-strict
) 시도하여 시작하는 것이 좋습니다 . 스트리밍 라이브러리 (iostream, 파이프, 도관, 스트리밍)를 사용하고 performGC
더 빈번한 간격으로 직접 호출 해보십시오 .
MutableByteArray
; GC 의 링 버퍼 는 전혀 관여하지 않을 것입니다)