รอยเท้าหน่วยความจำของชนิดข้อมูล Haskell


124

ฉันจะหาจำนวนหน่วยความจำจริงที่ต้องใช้ในการจัดเก็บค่าของข้อมูลบางประเภทใน Haskell (ส่วนใหญ่เป็น GHC) ได้อย่างไร เป็นไปได้ไหมที่จะประเมินที่รันไทม์ (เช่นใน GHCi) หรือสามารถประเมินความต้องการหน่วยความจำของชนิดข้อมูลผสมจากส่วนประกอบได้หรือไม่

โดยทั่วไปหากทราบข้อกำหนดของหน่วยความจำประเภทaและbค่าใช้จ่ายของหน่วยความจำประเภทข้อมูลพีชคณิตเช่น:

data Uno = Uno a
data Due = Due a b

ตัวอย่างเช่นค่าเหล่านี้ใช้หน่วยความจำกี่ไบต์?

1 :: Int8
1 :: Integer
2^100 :: Integer
\x -> x + 1
(1 :: Int8, 2 :: Int8)
[1] :: [Int8]
Just (1 :: Int8)
Nothing

ฉันเข้าใจว่าการจัดสรรหน่วยความจำจริงสูงกว่าเนื่องจากการรวบรวมขยะล่าช้า อาจแตกต่างกันอย่างมีนัยสำคัญเนื่องจากการประเมินแบบขี้เกียจ (และขนาดของชิ้นส่วนไม่เกี่ยวข้องกับขนาดของค่า) คำถามคือเมื่อพิจารณาถึงประเภทข้อมูลแล้วหน่วยความจำจะใช้ค่าเท่าใดเมื่อประเมินโดยสมบูรณ์?

ฉันพบว่ามี:set +sตัวเลือกใน GHCi เพื่อดูสถิติหน่วยความจำ แต่ยังไม่ชัดเจนว่าจะประมาณค่าหน่วยความจำของค่าเดียวได้อย่างไร

คำตอบ:


156

(ข้อมูลต่อไปนี้ใช้กับ GHC คอมไพเลอร์อื่น ๆ อาจใช้รูปแบบการจัดเก็บที่แตกต่างกัน)

กฎของหัวแม่มือ: นวกรรมิกค่าใช้จ่ายหนึ่งคำสำหรับส่วนหัวและคำเดียวสำหรับแต่ละฟิลด์ ข้อยกเว้น: ตัวสร้างที่ไม่มีฟิลด์ (เช่นNothingหรือTrue) ไม่ต้องใช้พื้นที่เนื่องจาก GHC สร้างอินสแตนซ์เดียวของตัวสร้างเหล่านี้และใช้ร่วมกันระหว่างการใช้งานทั้งหมด

คำคือ 4 ไบต์บนเครื่อง 32 บิตและ 8 ไบต์บนเครื่อง 64 บิต

เช่น

data Uno = Uno a
data Due = Due a b

Unoใช้เวลา 2 คำและDueใช้เวลา 3

Intประเภทถูกกำหนดให้เป็น

data Int = I# Int#

ตอนนี้Int#ใช้คำเดียวดังนั้นIntใช้ทั้งหมด 2 ส่วนใหญ่ไม่มีกล่องชนิดใช้เวลาหนึ่งคำข้อยกเว้นที่เป็นInt64#, Word64#และDouble#(บนเครื่อง 32 บิต) ซึ่งใช้เวลา 2 GHC จริงมีแคชของค่าเล็ก ๆ ของประเภทIntและCharดังนั้นในหลายกรณีเหล่านี้ใช้พื้นที่กองที่ไม่ทั้งหมด Stringเพียง แต่ต้องใช้พื้นที่สำหรับเซลล์รายการเว้นแต่คุณจะใช้Chars> 255

มีการแสดงเหมือนกัน Int8 ถูกกำหนดไว้เช่นนี้:IntInteger

data Integer
  = S# Int#                            -- small integers
  | J# Int# ByteArray#                 -- large integers

ดังนั้น small Integer( S#) จึงใช้เวลา 2 คำ แต่จำนวนเต็มขนาดใหญ่จะใช้พื้นที่ผันแปรได้ขึ้นอยู่กับค่าของมัน A ByteArray#ใช้เวลา 2 คำ (ส่วนหัว + ขนาด) บวกช่องว่างสำหรับอาร์เรย์เอง

โปรดทราบว่าตัวสร้างที่กำหนดไว้กับnewtypeฟรี newtypeเป็นแนวคิดในการรวบรวมเวลาเท่านั้นและไม่ใช้พื้นที่ว่างและไม่มีคำแนะนำใด ๆ ในขณะดำเนินการ

รายละเอียดเพิ่มเติมในรูปแบบของกองวัตถุในอรรถกถา GHC


1
ขอบคุณไซมอน นี่คือสิ่งที่ฉันอยากรู้
sastanin

2
ส่วนหัวสองคำไม่ใช่เหรอ หนึ่งสำหรับแท็กและอีกตัวสำหรับตัวชี้การส่งต่อสำหรับใช้ระหว่าง GC หรือการประเมินผล? นั่นจะไม่เพิ่มหนึ่งคำในผลรวมของคุณหรือ?
Edward KMETT

5
@Edward: Thunks ถูกเขียนทับโดย indirections (ซึ่งต่อมาจะถูกลบออกโดย GC) แต่คำเหล่านี้มีเพียง 2 คำเท่านั้นและทุกออบเจ็กต์ฮีปจะมีขนาดอย่างน้อย 2 คำ ไม่มีการสร้างโปรไฟล์หรือคุณสมบัติการแก้ไขจุดบกพร่องใด ๆ ที่เปิดอยู่บนส่วนหัวจริงๆเป็นเพียงคำเดียว ใน GHC นั่นคือการนำไปใช้งานอื่น ๆ อาจทำสิ่งต่างออกไป
nominolo

3
nominolo: ใช่ แต่จาก Closure.h: / * A thunk มีคำขยายเพื่อรับค่าที่อัปเดต เพื่อให้การอัปเดตไม่เขียนทับ payload ดังนั้นเราจึงไม่จำเป็นต้องล็อก thunk ระหว่างการเข้าและอัปเดต หมายเหตุ: สิ่งนี้ใช้ไม่ได้กับ THUNK_STATIC ซึ่งไม่มีเพย์โหลด หมายเหตุ: เราทิ้งคำเติมนี้ไว้ในทุก ๆ ด้านแทนที่จะเป็นเพียง SMP เพื่อที่เราจะได้ไม่ต้องคอมไพล์ไลบรารีทั้งหมดของเราสำหรับ SMP * / payload จะไม่ถูกเขียนทับในระหว่างการกำหนดทิศทาง การกำหนดทิศทางถูกเขียนในตำแหน่งที่แยกต่างหากในส่วนหัว
Edward KMETT

6
ใช่ แต่ทราบว่านี่เป็นสำหรับthunksเท่านั้น ไม่ใช้กับผู้สร้าง การประมาณขนาดของ thunk นั้นค่อนข้างยากอยู่แล้ว - คุณต้องนับตัวแปรอิสระ
nominolo

4

แพคเกจ ghc-datasize มีฟังก์ชันrecursiveSizeเพื่อคำนวณขนาดของวัตถุ GHC อย่างไรก็ตาม ...

การเก็บขยะจะดำเนินการก่อนที่จะคำนวณขนาดเนื่องจากคนเก็บขยะจะทำให้การเดินของกองขยะทำได้ยาก

... ดังนั้นมันจะไม่เป็นประโยชน์ที่จะเรียกสิ่งนี้บ่อยๆ!

ดูวิธีค้นหาการแสดงหน่วยความจำประเภทข้อมูลของ GHC ได้อย่างไร และฉันจะกำหนดขนาดของประเภทใน Haskell ได้อย่างไร? .

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.