การลดเวลาหยุดการรวบรวมขยะในโปรแกรม Haskell


130

เรากำลังพัฒนาโปรแกรมที่รับและส่งต่อ "ข้อความ" ในขณะที่เก็บประวัติชั่วคราวของข้อความเหล่านั้นเพื่อให้สามารถบอกประวัติข้อความหากมีการร้องขอ ข้อความจะถูกระบุเป็นตัวเลขโดยทั่วไปจะมีขนาดประมาณ 1 กิโลไบต์และเราจำเป็นต้องเก็บรักษาข้อความเหล่านี้นับแสนรายการ

เราต้องการเพิ่มประสิทธิภาพโปรแกรมนี้สำหรับเวลาแฝง: เวลาระหว่างการส่งและรับข้อความต้องน้อยกว่า 10 มิลลิวินาที

โปรแกรมนี้เขียนใน Haskell และคอมไพล์ด้วย GHC อย่างไรก็ตามเราพบว่าการเก็บรวบรวมขยะหยุดชั่วคราวนานเกินไปสำหรับข้อกำหนดด้านเวลาแฝงของเรา: มากกว่า 100 มิลลิวินาทีในโปรแกรมจริงของเรา

โปรแกรมต่อไปนี้เป็นเวอร์ชันที่เรียบง่ายของแอปพลิเคชันของเรา มันใช้Data.Map.Strictเพื่อเก็บข้อความ ข้อความจะถูกByteStrings 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 ที่เล็กกว่านอกเหนือจากการแบ่งเป็นหลาย ๆ กระบวนการ?

1
@Bakuriu: ถูกต้อง แต่ 10 ms ควรจะทำได้ด้วย OS ที่ทันสมัยมาก ๆ โดยไม่มีการปรับแต่งใด ๆ เมื่อฉันรันโปรแกรม C แบบง่าย ๆ แม้ใน Raspberry pi ตัวเก่าของพวกมันพวกมันสามารถให้เวลาในการตอบสนองได้อย่างง่ายดายในช่วง 5 มิลลิวินาทีหรืออย่างน้อยก็น่าเชื่อถืออย่างเช่น 15 ms
leftaroundabout

3
คุณมั่นใจว่ากรณีทดสอบของคุณมีประโยชน์หรือไม่ (เช่นคุณไม่ได้ใช้COntrol.Concurrent.Chanงานหรือไม่วัตถุที่เปลี่ยนแปลงได้เปลี่ยนสมการ) ฉันขอแนะนำให้เริ่มต้นด้วยการทำให้แน่ใจว่าคุณรู้ว่าสิ่งที่คุณสร้างขยะกำลังและการทำน้อยได้ว่ามันเป็นไปได้ (เช่นให้แน่ใจว่าฟิวชั่นที่เกิดขึ้นลอง-funbox-strict) อาจลองใช้การสตรีม lib (iostreams, pipe, conduit, streaming) และการโทรperformGCโดยตรงเป็นระยะบ่อยครั้งมากขึ้น
jberryman

6
หากสิ่งที่คุณพยายามจะทำให้สำเร็จสามารถทำได้ในพื้นที่คงที่ให้เริ่มต้นด้วยการพยายามทำให้เกิดขึ้น (เช่นอาจเป็นบัฟเฟอร์วงแหวนจาก a MutableByteArray; GC จะไม่เกี่ยวข้องเลยในกรณีนี้)
jberryman

1
สำหรับผู้ที่แนะนำโครงสร้างที่ไม่แน่นอนและดูแลเพื่อสร้างขยะน้อยที่สุดโปรดทราบว่ามันเป็นขนาดที่เก็บไว้ไม่ใช่จำนวนของขยะที่เก็บรวบรวมซึ่งปรากฏขึ้นเพื่อกำหนดเวลาหยุดชั่วคราว การบังคับให้คอลเล็กชันบ่อยขึ้นจะส่งผลให้มีความยาวเท่ากันมากขึ้น แก้ไข: โครงสร้าง off-heap ที่เปลี่ยนแปลงไม่ได้อาจน่าสนใจ แต่ไม่สนุกนักในหลาย ๆ กรณี!
ไมค์

6
คำอธิบายนี้แสดงให้เห็นอย่างแน่นอนว่าเวลา GC จะเป็นเส้นตรงในขนาดของฮีปสำหรับทุกชั่วอายุปัจจัยสำคัญคือขนาดของวัตถุที่ถูกเก็บไว้ (สำหรับการคัดลอก) และจำนวนพอยน์เตอร์ที่มีอยู่ (สำหรับการกวาด): ghc.haskell org / trac / ghc / wiki / Commentary / Rts / Storage / GC / การคัดลอก
ไมค์

คำตอบ:


96

คุณทำได้ดีจริง ๆ เพื่อให้มีเวลาหยุดชั่วคราว 51ms ด้วยข้อมูลสดมากกว่า 200Mb ระบบที่ฉันทำงานมีเวลาหยุดชั่วคราวมากขึ้นโดยมีข้อมูลสดครึ่งหนึ่ง

สมมติฐานของคุณถูกต้องเวลาหยุดชั่วคราวหลักของ GC จะเป็นสัดส่วนโดยตรงกับปริมาณข้อมูลสดและน่าเสียดายที่ GHC ไม่มีวิธีแก้ไข เราได้ทดลองกับ GC ที่เพิ่มขึ้นในอดีต แต่มันเป็นโครงการวิจัยและไม่ถึงระดับของวุฒิภาวะที่จำเป็นในการพับลงใน GHC ที่วางจำหน่าย

สิ่งหนึ่งที่เราหวังว่าจะช่วยได้ในอนาคตคือภูมิภาคเล็ก: https://phabricator.haskell.org/D1264 https://phabricator.haskell.org/D1264เป็นการจัดการหน่วยความจำแบบแมนนวลที่คุณทำการกระชับโครงสร้างในฮีพและ GC ไม่จำเป็นต้องทำการสำรวจ มันทำงานได้ดีที่สุดสำหรับข้อมูลที่มีอายุการใช้งานยาวนาน แต่บางทีมันอาจจะดีพอที่จะใช้สำหรับแต่ละข้อความในการตั้งค่าของคุณ เรามุ่งหวังที่จะมีมันใน GHC 8.2.0

หากคุณอยู่ในการตั้งค่าแบบกระจายและมี load-balancer บางชนิดมีกลอุบายที่คุณสามารถเล่นเพื่อหลีกเลี่ยงการหยุดชั่วคราวคุณต้องแน่ใจว่า load-balancer ไม่ส่งคำขอไปยังเครื่องที่กำลังจะไป ทำ GC สำคัญและแน่นอนตรวจสอบให้แน่ใจว่าเครื่องยังคงทำ GC ให้เสร็จสมบูรณ์แม้ว่าจะไม่ได้รับคำขอก็ตาม


13
สวัสดี Simon ขอบคุณมากสำหรับคำตอบอย่างละเอียดของคุณ! มันเป็นข่าวร้าย แต่ก็ดีที่ต้องปิดตัวลง ขณะนี้เรากำลังก้าวไปสู่การปรับใช้ที่ไม่แน่นอนซึ่งเป็นทางเลือกเดียวที่เหมาะสม บางสิ่งที่เราไม่เข้าใจ: (1) เทคนิคใดบ้างที่เกี่ยวข้องกับโครงร่างการทำโหลดบาลานซ์ - พวกเขาเกี่ยวข้องกับคู่มือperformGCหรือไม่? (2) ทำไมการบีบอัดด้วย-cประสิทธิภาพที่แย่ลง - เราคิดว่าเพราะมันไม่พบหลายสิ่งที่สามารถออกจากที่ได้ (3) มีรายละเอียดเพิ่มเติมเกี่ยวกับคอมแพคหรือไม่ มันฟังดูน่าสนใจมาก แต่น่าเสียดายที่มันค่อนข้างไกลเกินไปในอนาคตที่เราจะต้องพิจารณา
jameshfisher

2
@mljrg คุณอาจสนใจwell-typed.com/blog/2019/10/nonmoving-gc-merge
Alfredo Di Napoli

@AlfredoDiNapoli ขอบคุณ!
mljrg

9

ฉันได้ลองใช้โค้ดขนาดสั้นของคุณด้วยวิธีใช้ริงค์ฟูกเกอร์แล้ว IOVectorเป็นโครงสร้างข้อมูลพื้นฐาน ในระบบของฉัน (GHC 7.10.3 ตัวเลือกการรวบรวมเดียวกัน) ส่งผลให้ลดเวลาสูงสุด (ตัวชี้วัดที่คุณกล่าวถึงใน OP) ได้ประมาณ 22%

NB ฉันทำสมมติฐานสองข้อที่นี่:

  1. โครงสร้างข้อมูลที่เปลี่ยนแปลงไม่ได้เป็นแบบที่เหมาะสมสำหรับปัญหา (ฉันเดาว่าการส่งข้อความหมายถึง IO แต่อย่างใด)
  2. รหัสข้อความของคุณนั้นต่อเนื่อง

ด้วยIntพารามิเตอร์เพิ่มเติมและเลขคณิตบางอย่าง(เช่นเมื่อ messageId ถูกรีเซ็ตกลับเป็น 0 หรือminBound ) มันควรจะตรงไปตรงมาเพื่อตรวจสอบว่าข้อความบางอย่างยังอยู่ในประวัติ

เพื่อความสุขในการทดสอบของคุณ:

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

import qualified Data.Vector.Mutable as Vector

data Msg = Msg !Int !ByteString.ByteString

type Chan = Map.Map Int ByteString.ByteString

data Chan2 = Chan2
    { next          :: !Int
    , maxId         :: !Int
    , ringBuffer    :: !(Vector.IOVector ByteString.ByteString)
    }

chanSize :: Int
chanSize = 200000

message :: Int -> Msg
message n = Msg n (ByteString.replicate 1024 (fromIntegral n))


newChan2 :: IO Chan2
newChan2 = Chan2 0 0 <$> Vector.unsafeNew chanSize

pushMsg2 :: Chan2 -> Msg -> IO Chan2
pushMsg2 (Chan2 ix _ store) (Msg msgId msgContent) =
    let ix' = if ix == chanSize then 0 else ix + 1
    in Vector.unsafeWrite store ix' msgContent >> return (Chan2 ix' msgId store)

pushMsg :: Chan -> Msg -> IO Chan
pushMsg chan (Msg msgId msgContent) =
  Exception.evaluate $
    let
      inserted = Map.insert msgId msgContent chan
    in
      if chanSize < Map.size inserted
      then Map.deleteMin inserted
      else inserted

main, main1, main2 :: IO ()

main = main2

main1 = Monad.foldM_ pushMsg Map.empty (map message [1..1000000])

main2 = newChan2 >>= \c -> Monad.foldM_ pushMsg2 c (map message [1..1000000])

2
Hi! คำตอบที่ดี ฉันสงสัยว่านี่เป็นเพียงการเพิ่มความเร็ว 22% เท่านั้นเพราะ GC ยังคงต้องเดินต่อไปIOVectorและค่า (ไม่เปลี่ยนรูป, GC'd) ที่แต่ละดัชนี ขณะนี้เรากำลังตรวจสอบตัวเลือกสำหรับการนำกลับมาใช้ใหม่โดยใช้โครงสร้างที่ไม่แน่นอน มีแนวโน้มที่จะคล้ายกับระบบบัฟเฟอร์ของคุณ แต่เรากำลังเคลื่อนย้ายมันออกนอกพื้นที่หน่วยความจำของ Haskell เพื่อจัดการหน่วยความจำด้วยตนเองของเราเอง
jameshfisher

11
@jamesfisher: จริง ๆ แล้วฉันกำลังเผชิญกับปัญหาที่คล้ายกัน แต่ตัดสินใจที่จะให้การจัดการ mem ที่ด้าน Haskell วิธีการแก้ปัญหานั้นเป็นวงแหวนบัฟเฟอร์ซึ่งเก็บสำเนาต้นฉบับของข้อมูลไว้ในบล็อกหน่วยความจำเดี่ยวต่อเนื่องซึ่งส่งผลให้มีค่า Haskell เดียว มีลักษณะที่มันอยู่ในนี้เค้า RingBuffer.hs ฉันทดสอบกับโค้ดตัวอย่างของคุณและมีการเร่งความเร็วประมาณ 90% ของการวัดที่สำคัญ รู้สึกอิสระที่จะใช้รหัสตามความสะดวกของคุณ
mgmeier

8

ฉันต้องเห็นด้วยกับคนอื่น - ถ้าคุณมีข้อ จำกัด แบบเรียลไทม์ยากการใช้ภาษา GC นั้นไม่เหมาะ

อย่างไรก็ตามคุณอาจลองทำการทดสอบกับโครงสร้างข้อมูลอื่นที่มีอยู่แทน Data.Map

ฉันเขียนใหม่โดยใช้ Data.Sequence และได้รับการปรับปรุงที่มีแนวโน้ม:

msgs history length  max GC pause (ms)
===================  =================
12500                              0.7
25000                              1.4
50000                              2.8
100000                             5.4
200000                            10.9
400000                            21.8
800000                            46
1600000                           87
3200000                          175
6400000                          350

แม้ว่าคุณจะเพิ่มประสิทธิภาพสำหรับเวลาแฝง แต่ฉันก็สังเกตเห็นการปรับปรุงอื่น ๆ ด้วย ในกรณี 200000 เวลาดำเนินการลดลงจาก 1.5s เป็น 0.2s และการใช้หน่วยความจำทั้งหมดลดลงจาก 600MB ถึง 27MB

ฉันควรทราบว่าฉันโกงด้วยการออกแบบ tweaking:

  • ฉันลบรายการออกIntจากรายการMsgดังนั้นจึงไม่อยู่ในที่สองแห่ง
  • แทนการใช้แผนที่Intเพื่อByteStrings ผมใช้SequenceของByteStrings และแทนหนึ่งIntต่อข้อความผมคิดว่ามันสามารถทำได้ด้วยหนึ่งสำหรับทั้งInt Sequenceสมมติว่าข้อความไม่สามารถจัดลำดับใหม่ได้คุณสามารถใช้ออฟเซ็ตเดียวเพื่อแปลข้อความที่คุณต้องการไปยังตำแหน่งที่อยู่ในคิว

(ฉันรวมฟังก์ชั่นเพิ่มเติมgetMsgเพื่อแสดงให้เห็น)

{-# LANGUAGE BangPatterns #-}

import qualified Control.Exception as Exception
import qualified Control.Monad as Monad
import qualified Data.ByteString as ByteString
import Data.Sequence as S

newtype Msg = Msg ByteString.ByteString

data Chan = Chan Int (Seq ByteString.ByteString)

message :: Int -> Msg
message n = Msg (ByteString.replicate 1024 (fromIntegral n))

maxSize :: Int
maxSize = 200000

pushMsg :: Chan -> Msg -> IO Chan
pushMsg (Chan !offset sq) (Msg msgContent) =
    Exception.evaluate $
        let newSize = 1 + S.length sq
            newSq = sq |> msgContent
        in
        if newSize <= maxSize
            then Chan offset newSq
            else
                case S.viewl newSq of
                    (_ :< newSq') -> Chan (offset+1) newSq'
                    S.EmptyL -> error "Can't happen"

getMsg :: Chan -> Int -> Maybe Msg
getMsg (Chan offset sq) i_ = getMsg' (i_ - offset)
    where
    getMsg' i
        | i < 0            = Nothing
        | i >= S.length sq = Nothing
        | otherwise        = Just (Msg (S.index sq i))

main :: IO ()
main = Monad.foldM_ pushMsg (Chan 0 S.empty) (map message [1..5 * maxSize])

4
Hi! ขอบคุณสำหรับคำตอบ. ผลลัพธ์ของคุณยังคงแสดงการชะลอตัวเชิงเส้นอย่างแน่นอน แต่น่าสนใจมากที่คุณได้รับการเร่งความเร็วจากData.Sequence- เราทดสอบและพบว่ามันแย่กว่า Data.Map จริง ๆ ! ฉันไม่แน่ใจว่าความแตกต่างคืออะไรดังนั้นฉันจะต้องตรวจสอบ ...
jameshfisher

8

ดังที่กล่าวไว้ในคำตอบอื่น ๆ ตัวรวบรวมขยะใน GHC สำรวจข้อมูลสดซึ่งหมายความว่ายิ่งคุณเก็บข้อมูลไว้นานในหน่วยความจำยิ่ง GC ยิ่งหยุดทำงานนานเท่าใด

GHC 8.2

เพื่อแก้ไขปัญหานี้บางส่วนได้มีการแนะนำคุณลักษณะที่เรียกว่าภูมิภาคที่มีขนาดกะทัดรัดใน GHC-8.2 มันเป็นทั้งคุณสมบัติของระบบรันไทม์ GHC และห้องสมุดซึ่ง exposes สะดวกอินเตอร์เฟซที่จะทำงานร่วมกับ คุณสมบัติพื้นที่ขนาดกะทัดรัดช่วยให้การใส่ข้อมูลของคุณในสถานที่แยกต่างหากในหน่วยความจำและ GC จะไม่เข้าไปในระหว่างการรวบรวมขยะ ดังนั้นหากคุณมีโครงสร้างขนาดใหญ่ที่คุณต้องการเก็บไว้ในหน่วยความจำให้พิจารณาใช้พื้นที่ขนาดกะทัดรัด อย่างไรก็ตามพื้นที่ขนาดกะทัดรัดนั้นไม่มี ตัวรวบรวมขยะขนาดเล็กอยู่ภายในจะทำงานได้ดีขึ้นสำหรับโครงสร้างข้อมูลแบบผนวกเท่านั้นไม่ใช่สิ่งHashMapที่คุณต้องการลบข้อมูล แม้ว่าคุณจะสามารถเอาชนะปัญหานี้ได้ สำหรับรายละเอียดอ้างอิงถึงโพสต์บล็อกต่อไปนี้:

GHC 8.10

ยิ่งกว่านั้นตั้งแต่ GHC-8.10 จึงมีการใช้อัลกอริทึมตัวรวบรวมขยะแบบเพิ่มความล่าช้าต่ำ เป็นอัลกอริทึม GC ทางเลือกอื่นซึ่งไม่ได้เปิดใช้งานโดยค่าเริ่มต้น แต่คุณสามารถเลือกได้หากต้องการ ดังนั้นคุณสามารถเปลี่ยน GC เริ่มต้นเป็นรุ่นใหม่เพื่อรับคุณสมบัติที่มีให้โดยอัตโนมัติในภูมิภาคขนาดกะทัดรัดโดยไม่จำเป็นต้องทำการห่อด้วยมือและแกะห่อใหม่ อย่างไรก็ตาม GC ใหม่ไม่ใช่ bullet เงินและไม่ได้แก้ปัญหาทั้งหมดโดยอัตโนมัติและมีการแลกเปลี่ยน สำหรับการวัดประสิทธิภาพของ GC ใหม่ให้อ้างอิงที่เก็บ GitHub ต่อไปนี้:


3

คุณพบข้อ จำกัด ของภาษาด้วย GC: ภาษาเหล่านี้ไม่เหมาะกับระบบแบบเรียลไทม์ฮาร์ดคอร์

คุณมี 2 ตัวเลือก:

1st เพิ่มขนาดฮีปและใช้ระบบแคช 2 ระดับข้อความที่เก่าที่สุดจะถูกส่งไปยังดิสก์และคุณเก็บข้อความใหม่ล่าสุดไว้ในหน่วยความจำคุณสามารถทำได้โดยใช้การเพจจิ้ง ปัญหาแม้ว่าด้วยวิธีนี้คือการเพจอาจมีราคาแพงขึ้นอยู่กับความสามารถในการอ่านของหน่วยความจำรองที่ใช้

โปรแกรมที่ 2 ที่ใช้ 'C' และเชื่อมต่อกับ FFI เพื่อ Haskell ด้วยวิธีนี้คุณสามารถจัดการหน่วยความจำของคุณเองได้ นี่จะเป็นตัวเลือกที่ดีที่สุดเพราะคุณสามารถควบคุมหน่วยความจำที่คุณต้องการได้ด้วยตัวเอง


1
สวัสดีเฟอร์นันโด ขอบคุณสำหรับสิ่งนี้. ระบบของเราเป็นแบบเรียลไทม์ "อ่อน" แต่ในกรณีของเราเราพบว่า GC ถูกลงโทษมากเกินไปแม้ในเวลาจริง เราเรียนรู้วิธีแก้ปัญหา # 2 ของคุณอย่างแน่นอน
jameshfisher
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.