Memory Management
Levython uses automatic memory management with garbage collection and NaN-boxing for efficient value representation.
NaN-Boxing
All values in Levython are stored as 64-bit NaN-boxed values:
| Type | Representation | Range |
|---|---|---|
| Float | IEEE 754 double | Full double precision |
| Integer | 53-bit signed int | -2^53 to 2^53 |
| Boolean | Special NaN pattern | true/false |
| Pointer | 48-bit pointer in NaN | Heap objects |
Memory Layout
# Integers - no heap allocation
x <- 42 # 8 bytes on stack
# Strings - heap allocated
name <- "Alice" # 8 bytes (pointer) + string data on heap
# Lists - heap allocated with capacity
items <- [1, 2, 3] # 8 bytes (pointer) + 24 bytes header + 24 bytes data
Garbage Collection
Levython uses mark-and-sweep garbage collection:
GC Phases
- Mark: Trace from roots and mark reachable objects
- Sweep: Free unmarked objects
- Compact: Optional heap defragmentation
When GC Runs
- When heap usage exceeds threshold (default: 8MB)
- After allocating large objects (>1MB)
- Manually via
gc()function
# Create temporary objects
for i in range(0, 1000) do
temp <- [i] * 1000 # Large temporary list
end
# GC automatically runs and frees unused memory
Memory-Efficient Patterns
Reuse Objects
# Bad - creates new list each iteration
for i in range(0, 1000) do
data <- []
for j in range(0, 100) do
append(data, j)
end
process(data)
end
# Good - reuse same list
data <- []
for i in range(0, 1000) do
clear(data) # Clear instead of creating new
for j in range(0, 100) do
append(data, j)
end
process(data)
end
Avoid Intermediate Lists
# Bad - creates intermediate lists
numbers <- range(0, 1000)
evens <- [x for x in numbers if x % 2 == 0]
doubled <- [x * 2 for x in evens]
# Good - single pass
doubled_evens <- [x * 2 for x in range(0, 1000) if x % 2 == 0]
Use Generators (Coming Soon)
# Future feature: generators for lazy evaluation
# gen fibonacci()
# a <- 0
# b <- 1
# while true do
# yield a
# temp <- a
# a <- b
# b <- temp + b
# end
# end
Memory Profiling
Check Memory Usage
# Get current memory usage
before <- memory()
say("Memory before: " + str(before) + " bytes")
# Create some data
big_list <- [0] * 1000000
after <- memory()
say("Memory after: " + str(after) + " bytes")
say("Allocated: " + str(after - before) + " bytes")
Force Garbage Collection
# Create temporary objects
temp <- [0] * 10000000 # 80MB list
temp <- null # Release reference
# Force GC to reclaim memory
gc()
say("Memory freed")
Object Sizes
| Object Type | Base Size | Per Element |
|---|---|---|
| Integer | 0 bytes | NaN-boxed inline |
| Float | 0 bytes | NaN-boxed inline |
| Boolean | 0 bytes | NaN-boxed inline |
| String | 24 bytes | 1 byte per char |
| List | 24 bytes | 8 bytes per element |
| Function | 48 bytes | + bytecode size |
Memory Leaks Prevention
Common Pitfalls
# Bad - growing list never shrinks
cache <- []
while running do
data <- fetch_data()
append(cache, data) # Cache grows indefinitely
end
# Good - limit cache size
cache <- []
max_size <- 1000
while running do
data <- fetch_data()
append(cache, data)
if len(cache) > max_size then
remove(cache, 0) # Remove oldest entry
end
end
Circular References
Levython's GC handles circular references automatically:
# Circular reference - automatically handled
node1 <- {"value": 1, "next": null}
node2 <- {"value": 2, "next": null}
node1["next"] <- node2
node2["next"] <- node1 # Circular
# When node1 and node2 go out of scope, GC frees both
Best Practices
- ✅ Reuse objects instead of creating new ones in loops
- ✅ Limit collection sizes (implement bounded caches)
- ✅ Clear references when done (
obj <- null) - ✅ Prefer list comprehensions over manual building
- ✅ Use
gc()after large deallocations - ✅ Profile memory usage with
memory() - ❌ Don't keep references to large temporary objects
- ❌ Don't create unbounded caches without eviction
- ❌ Don't ignore memory growth in long-running processes