GHC Memory Visualizer

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.

🚧 Under Construction
This tool is actively being developed. Features may change or behave unexpectedly.

Type Definition

Tip: Enter raw type expressions: Int → Bool, (Int, Bool), (# Int#, Double# #)

Examples

Built-in types:
String, Tuple, Maybe, Either, List, IO, ST
Unboxed: (#,#) tuples, (#|#) sums
Functions: Int → Bool, a → b → c

Memory Layout

Enter a type definition to see memory layout

Controls

Share your current workspace via URL

Legend

Memory Cell Types

Header
Object metadata (8B)
Pointer
Points to heap object (8B)
Unboxed
Inline primitive value
Mutable
Mutable reference
Recursive
Self or mutual recursion
Phantom
Zero runtime size (0B)

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

  1. Define your data type in the "Type Definition" field
  2. Specify a concrete instantiation in "Concrete Type to Expand"
  3. Observe the memory layout, including all pointer chains
  4. Check the "Memory Analysis" summary for size breakdowns
  5. Try the array examples to understand card tables