GHC Memory Layout Visualizer
Visualize how GHC represents Haskell data types in memory. Understand heap layouts, pointer indirection, unboxed primitives, card tables for arrays, and recursive type detection. Essential for writing performant Haskell code.
Type Definition
Tip: Enter raw type expressions: Int → Bool, (Int, Bool), (# Int#, Double# #)
Examples
Memory Layout
Controls
Share your current workspace via URL
Legend
Memory Cell Types
Newtype: Zero runtime overhead
Unboxed tuples: No allocation
Card tables: For arrays ≥128 elements
Help
Quick Guide
- Header: Object metadata (8 bytes)
- Pointer: Heap object reference (8 bytes)
- Unboxed: Inline primitive, no indirection
- Recursive: Unbounded recursive reference
- Newtype: Zero runtime overhead
- Unboxed tuples/sums: No allocation
Tip: Use {-# UNPACK #-} pragmas to eliminate boxing overhead.
Understanding Memory Layouts
Why Does This Matter?
GHC's memory representation directly impacts:
- Performance: Boxing overhead can cost 10-100x in tight loops
- Memory usage: Naive structures can use 5-10x more memory
- GC pressure: More heap objects = more GC work
- Cache locality: Indirection causes cache misses
Key Concepts
Boxed vs Unboxed
Boxed: Int is a pointer to a heap object containing Int#.
Total: 16 bytes (8-byte header + 8-byte payload).
Unboxed: Int# is the raw machine integer, inline in its container.
No heap allocation, no indirection.
UNPACK Pragma
{-# UNPACK #-} !Double eliminates boxing overhead by storing the raw
Double# directly in the parent structure. Critical for numeric-heavy code.
Card Tables
For large arrays (≥128 elements), GHC uses card tables to track which 128-element "cards" contain pointers to young generation objects. During minor GC, only dirty cards need scanning, potentially reducing GC time by 50-90% for large, stable arrays.
Array Type Selection
SmallArray#: <128 elements, no card table overhead
Array#: ≥128 elements, uses card tables for efficient GC
ByteArray#: Unboxed data only, GC never scans contents
Real-World Impact
Example: Point type
-- Before
data Point = Point Double Double
-- 56 bytes: 8 (header) + 2×8 (ptrs) + 2×16 (Double boxes) -- After
data Point = Point {-# UNPACK #-} !Double {-# UNPACK #-} !Double
-- 24 bytes: 8 (header) + 2×8 (inline Double#) Result: 57% less memory, zero indirections, better cache locality
Example: Vector vs List
1000 Ints in a list: 40,008 bytes (2001 heap objects)
1000 Ints in an unboxed vector: 8,048 bytes (2 heap objects)
Savings: 80% memory, 99.9% fewer objects, vastly better cache behavior
Using This Tool
- Define your data type in the "Type Definition" field
- Specify a concrete instantiation in "Concrete Type to Expand"
- Observe the memory layout, including all pointer chains
- Check the "Memory Analysis" summary for size breakdowns
- Try the array examples to understand card tables