เรากำลังพัฒนาโปรแกรมที่รับและส่งต่อ "ข้อความ" ในขณะที่เก็บประวัติชั่วคราวของข้อความเหล่านั้นเพื่อให้สามารถบอกประวัติข้อความหากมีการร้องขอ ข้อความจะถูกระบุเป็นตัวเลขโดยทั่วไปจะมีขนาดประมาณ 1 กิโลไบต์และเราจำเป็นต้องเก็บรักษาข้อความเหล่านี้นับแสนรายการ
เราต้องการเพิ่มประสิทธิภาพโปรแกรมนี้สำหรับเวลาแฝง: เวลาระหว่างการส่งและรับข้อความต้องน้อยกว่า 10 มิลลิวินาที
โปรแกรมนี้เขียนใน Haskell และคอมไพล์ด้วย GHC อย่างไรก็ตามเราพบว่าการเก็บรวบรวมขยะหยุดชั่วคราวนานเกินไปสำหรับข้อกำหนดด้านเวลาแฝงของเรา: มากกว่า 100 มิลลิวินาทีในโปรแกรมจริงของเรา
โปรแกรมต่อไปนี้เป็นเวอร์ชันที่เรียบง่ายของแอปพลิเคชันของเรา มันใช้Data.Map.Strict
เพื่อเก็บข้อความ ข้อความจะถูกByteString
s 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 มิลลิวินาที เราต้องการลดสิ่งนี้อย่างน้อยลำดับความสำคัญ
การทดลองแสดงให้เห็นว่าความยาวของการหยุด 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
เราได้ทดลองกับตัวแปรอื่น ๆ เพื่อค้นหาว่าสามารถลดความหน่วงแฝงนี้ได้หรือไม่ ตัวแปรที่ไม่สำคัญเหล่านี้ ได้แก่ : optimization ( -O
, -O2
); ตัวเลือก RTS GC ( -G
, -H
, -A
, -c
) จำนวนแกน (-N
) โครงสร้างข้อมูลที่แตกต่างกัน (Data.Sequence
) ขนาดของข้อความและปริมาณของขยะที่สร้างสั้นอาศัยอยู่ ปัจจัยที่กำหนดอย่างท่วมท้นคือจำนวนข้อความในประวัติศาสตร์
ทฤษฎีการทำงานของเราคือการหยุดเป็นเส้นตรงในจำนวนข้อความเนื่องจากแต่ละรอบ GC ต้องเดินผ่านหน่วยความจำที่ใช้งานได้และคัดลอกมันซึ่งเป็นการดำเนินการเชิงเส้นอย่างชัดเจน
คำถาม:
- ทฤษฎีเชิงเส้นตรงนี้ถูกต้องหรือไม่? ความยาวของ GC หยุดชั่วคราวสามารถแสดงด้วยวิธีง่าย ๆ นี้หรือความจริงมีความซับซ้อนมากขึ้นหรือไม่?
- หากการหยุด GC ชั่วคราวเป็นเส้นตรงในหน่วยความจำการทำงานมีวิธีใดบ้างในการลดปัจจัยคงที่ที่เกี่ยวข้อง
- มีตัวเลือกใด ๆ สำหรับ GC ที่เพิ่มขึ้นหรืออะไรทำนองนั้น? เราสามารถดูรายงานการวิจัยเท่านั้น เรายินดีที่จะแลกเปลี่ยนปริมาณงานเพื่อลดความหน่วงแฝง
- มีวิธีใดบ้างในการ "แบ่งพาร์ติชัน" หน่วยความจำสำหรับวงรอบ GC ที่เล็กกว่านอกเหนือจากการแบ่งเป็นหลาย ๆ กระบวนการ?
COntrol.Concurrent.Chan
งานหรือไม่วัตถุที่เปลี่ยนแปลงได้เปลี่ยนสมการ) ฉันขอแนะนำให้เริ่มต้นด้วยการทำให้แน่ใจว่าคุณรู้ว่าสิ่งที่คุณสร้างขยะกำลังและการทำน้อยได้ว่ามันเป็นไปได้ (เช่นให้แน่ใจว่าฟิวชั่นที่เกิดขึ้นลอง-funbox-strict
) อาจลองใช้การสตรีม lib (iostreams, pipe, conduit, streaming) และการโทรperformGC
โดยตรงเป็นระยะบ่อยครั้งมากขึ้น
MutableByteArray
; GC จะไม่เกี่ยวข้องเลยในกรณีนี้)