"Mesajları" alan ve ileten, bu mesajların geçici bir geçmişini tutarken, istenirse size mesaj geçmişini söyleyebilecek bir program geliştiriyoruz. Mesajlar sayısal olarak tanımlanır, tipik olarak yaklaşık 1 kilobayt boyutundadır ve bu mesajlardan yüz binlerce saklamamız gerekir.
Bu programı gecikme için optimize etmek istiyoruz: mesaj gönderme ve alma arasındaki süre 10 milisaniyenin altında olmalıdır.
Program Haskell'de yazılmıştır ve GHC ile derlenmiştir. Ancak, çöp toplama duraklamalarının gecikme gereksinimlerimiz için çok uzun olduğunu bulduk: gerçek dünya programımızda 100 milisaniyenin üzerinde.
Aşağıdaki program, uygulamamızın basitleştirilmiş bir sürümüdür. Data.Map.Strict
Mesajları saklamak için a kullanır . Mesajlar bir ile ByteString
tanımlanır Int
. Artan sayısal sırada 1.000.000 mesaj eklenir ve en eski mesajlar, geçmişi maksimum 200.000 mesajda tutmak için sürekli olarak kaldırılır.
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])
Bu programı kullanarak derledik ve çalıştırdık:
$ 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
Buradaki önemli metrik, 0,0515 sn veya 51 milisaniye "maksimum duraklamadır". Bunu en azından bir dereceye kadar azaltmak istiyoruz.
Deney, bir GC duraklamasının uzunluğunun geçmişteki mesajların sayısına göre belirlendiğini gösterir. İlişki kabaca doğrusal veya belki de süper doğrusaldır. Aşağıdaki tablo bu ilişkiyi göstermektedir. ( Kıyaslama testlerimizi burada ve bazı grafikleri burada görebilirsiniz .)
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
Bu gecikmeyi azaltıp azaltamayacaklarını bulmak için başka değişkenlerle deneyler yaptık, bunların hiçbiri büyük bir fark yaratmıyor. Bu önemsiz değişkenler arasında şunlar vardır: optimizasyon ( -O
, -O2
); RTS GC seçenekleri ( -G
, -H
, -A
, -c
), çekirdek sayısı ( -N
), farklı bir veri yapıları ( Data.Sequence
), mesajların boyutunu ve oluşturulan kısa ömürlü çöp miktarı. En önemli belirleyici faktör, geçmişteki mesajların sayısıdır.
Çalışma teorimiz, duraklamaların mesaj sayısında doğrusal olmasıdır, çünkü her bir GC döngüsü çalışan erişilebilir tüm hafızanın üzerinde yürümek ve onu kopyalamak zorundadır ki bunlar açıkça doğrusal işlemlerdir.
Sorular:
- Bu doğrusal zaman teorisi doğru mu? GC duraklamalarının uzunluğu bu basit şekilde ifade edilebilir mi, yoksa gerçeklik daha karmaşık mı?
- GC duraklaması çalışma belleğinde doğrusal ise, ilgili sabit faktörleri azaltmanın herhangi bir yolu var mı?
- Artımlı GC için herhangi bir seçenek veya buna benzer herhangi bir seçenek var mı? Sadece araştırma makalelerini görebiliriz. Daha düşük gecikme süresi için iş hacmi ticareti yapmaya çok istekliyiz.
- Belleği daha küçük, GC döngüleri için, birden çok işleme bölmek dışında "bölümlemenin" herhangi bir yolu var mı?
COntrol.Concurrent.Chan
örneğin kullanmadığınız gibi? Değişken nesneler denklemi değiştirir)? Hangi çöpü ürettiğinizi bildiğinizden ve mümkün olduğunca az yaparak başlamanızı öneririm (örneğin, füzyonun gerçekleştiğinden emin olun, deneyin -funbox-strict
). Belki bir akış kitaplığı (iostreams, borular, kanal, akış) kullanmayı ve performGC
daha sık aralıklarla doğrudan arama yapmayı deneyebilirsiniz .
MutableByteArray
,