1. Resource Management

Efficient resource management is crucial for creating robust and performant PostScript programs. Understanding memory usage, resource allocation, and optimization techniques ensures your code runs efficiently on any device.

1.1. Overview

Resource management in PostScript includes:

  • Memory management - VM (Virtual Memory) allocation and usage

  • Object lifecycle - Creating and destroying objects

  • Resource pools - Fonts, patterns, and cached data

  • Stack management - Operand and dictionary stacks

  • Garbage collection - Automatic memory reclamation

  • Performance optimization - Efficient resource usage

Proper resource management prevents memory leaks, reduces overhead, and improves performance.

1.2. Memory Basics

1.2.1. Virtual Memory (VM)

PostScript uses VM for all objects:

% vmstatus: - vmstatus level used maximum
vmstatus
% Stack: level used maximum
% level = save/restore level
% used = bytes currently allocated
% maximum = maximum bytes available

Monitoring memory:

/showMemory {
  vmstatus
  (Memory status:) print
  ( Used: ) print exch =string cvs print
  ( / Maximum: ) print =string cvs print
  ( Level: ) print =string cvs print
  () print
  pop
} def

showMemory

1.2.2. Memory Allocation

Objects consume VM when created:

% Arrays
1000 array  % Allocates space for 1000 elements

% Strings
1024 string  % Allocates 1024 bytes

% Dictionaries
100 dict  % Allocates space for 100 key-value pairs

% Large objects consume more memory
/bigArray 100000 array def  % Significant allocation

1.2.3. Memory Deallocation

Memory is reclaimed through:

  1. Garbage collection - Automatic

  2. restore - Explicit VM cleanup

  3. save/restore - Local allocation

% Garbage collection happens automatically
/temp 10000 array def
/temp null def  % Makes array eligible for GC

% Force garbage collection (not always available)
% gc

1.3. Save and Restore

1.3.1. The save Operator

Creates a VM snapshot:

% save: - save savelevel
/snap save def

% Allocate objects
/data 1000 array def
/dict 50 dict def

% Objects created after save
% will be discarded on restore

1.3.2. The restore Operator

Returns to saved state:

save /snap exch def

% Create temporary objects
/temp1 1000 array def
/temp2 500 string def

% Restore - temp1 and temp2 are freed
snap restore

% temp1 and temp2 no longer exist

1.3.3. Save/Restore Pattern

/withLocalMemory {  % proc -> -
  save exch
  exec
  restore
} def

% Usage
{
  % All allocations here are temporary
  /tempData 10000 array def
  % Process data
} withLocalMemory
% tempData no longer exists

1.3.4. Nested Save/Restore

save /level1 exch def
  % Allocations at level 1

  save /level2 exch def
    % Allocations at level 2
  level2 restore

  % Level 2 allocations freed
  % Level 1 allocations remain
level1 restore
% All freed

1.4. Object Management

1.4.1. Object Lifecycle

% Creation
/myArray 100 array def

% Usage
myArray 0 42 put

% Release (make eligible for GC)
/myArray null def

1.4.2. Reusing Objects

% Good: reuse array
/buffer 1024 string def

% Use buffer multiple times
file buffer readstring pop
% ... process
file buffer readstring pop
% ... process

% Bad: create new each time
file 1024 string readstring pop
file 1024 string readstring pop
% ... allocates multiple buffers

1.4.3. Object Pools

/StringPool {
  10 dict begin
    /size 1024 def
    /pool 10 array def
    /index 0 def

    % Initialize pool
    0 1 pool length 1 sub {
      pool exch size string put
    } for

    /acquire {
      index pool length lt {
        pool index get
        /index index 1 add def
      } {
        % Pool exhausted, create new
        size string
      } ifelse
    } def

    /release {
      % Return to pool
      index 0 gt {
        /index index 1 sub def
        pool index 3 -1 roll put
      } {
        pop
      } ifelse
    } def

    currentdict
  end
} def

% Usage
StringPool /strPool exch def
strPool /acquire get exec  % Get string from pool
% ... use string
strPool /release get exec  % Return to pool

1.5. Stack Management

1.5.1. Operand Stack

Monitor and manage stack depth:

% count: - count n
count =  % Shows current stack depth

% Clear stack
clear

% Save stack state
count /initialDepth exch def
% ... operations
count initialDepth sub =  % Net stack change

1.5.2. Stack Hygiene

% Good: balanced stack
/goodProc {
  % Takes 2 args, leaves 1 result
  add
} def

% Bad: leaks values
/badProc {
  add
  42  % Oops, extra value left
} def

% Verify stack balance
/withStackCheck {  % proc -> -
  count exch
  exec
  count exch sub
  dup 0 ne {
    (Warning: Stack imbalance: ) print =
  } {
    pop
  } ifelse
} def

1.5.3. Dictionary Stack

Limit dictionary stack growth:

% Check dictionary stack depth
countdictstack =

% Good: use begin/end pairs
10 dict begin
  % Local definitions
end

% Bad: missing end
10 dict begin
  % Definitions
% Missing end - dict stays on stack!

1.6. Resource Pooling

1.6.1. Font Cache

/FontCache 50 dict def

/getCachedFont {  % fontname size -> font
  2 dict begin
    /size exch def
    /name exch def

    /key name =string cvs (-) exch concatstrings
         size =string cvs concatstrings def

    FontCache key known {
      FontCache key get
    } {
      name findfont size scalefont
      dup FontCache key 3 -1 roll put
    } ifelse
  end
} def

% Usage
/Times-Roman 12 getCachedFont setfont  % Cached
/Times-Roman 12 getCachedFont setfont  % Retrieved from cache

1.6.2. Pattern Cache

/PatternCache <<>> def

/cachePattern {  % name pattern -> -
  PatternCache 3 1 roll put
} def

/getPattern {  % name -> pattern
  PatternCache exch get
} def

% Usage
/dots {
  % ... dot pattern definition
} bind def

/dots (dots) cachePattern
(dots) getPattern exec  % Use cached pattern

1.6.3. Data Structure Reuse

/BufferManager {
  20 dict begin
    /buffers <<
      /small 256
      /medium 1024
      /large 4096
    >> def

    /cache <<>> def

    /get {  % size -> buffer
      buffers exch known {
        buffers 1 index get
        cache 2 index known {
          cache exch get
        } {
          string
        } ifelse
      } {
        string
      } ifelse
    } def

    /return {  % size buffer -> -
      cache 3 1 roll put
    } def

    currentdict
  end
} def

1.7. Memory Optimization

1.7.1. Minimize Allocations

% Good: allocate once, reuse
/buffer 1024 string def
0 1 99 {
  pop
  file buffer readstring pop
  % Process buffer
} for

% Bad: allocate every time
0 1 99 {
  pop
  file 1024 string readstring pop
  % New allocation each iteration
} for

1.7.2. Use Appropriate Sizes

% Good: right-sized allocation
/smallData 10 array def
/mediumData 100 array def
/largeData 1000 array def

% Bad: oversized allocation
/smallData 10000 array def  % Wastes memory

1.7.3. Packed Arrays

More memory-efficient for read-only data:

% Regular array (mutable, larger)
/data [1 2 3 4 5] def

% Packed array (read-only, smaller)
/data [1 2 3 4 5] cvx def

% Or using packedarray
mark 1 2 3 4 5 packedarray /data exch def

1.7.4. String Optimization

% Good: single string
/message (This is a long message) def

% Bad: concatenated strings
/message (This ) (is ) (a ) (long ) (message)
         concatstrings exch concatstrings exch concatstrings
         exch concatstrings def

1.8. Resource Monitoring

1.8.1. Memory Usage Tracking

/MemoryTracker {
  10 dict begin
    /snapshots [] def

    /snapshot {  % label -> -
      vmstatus pop
      2 array astore
      2 array astore
      /snapshots [ snapshots aload pop 3 -1 roll ] def
    } def

    /report {
      (Memory Usage Report:) print
      snapshots {
        aload pop
        (  ) print exch =string cvs print
        (: ) print
        aload pop
        ( bytes) exch =string cvs concatstrings print
      } forall
    } def

    currentdict
  end
} def

% Usage
MemoryTracker /tracker exch def
tracker /snapshot (Start) exec
% ... operations
tracker /snapshot (After operation) exec
tracker /report exec

1.8.2. Performance Profiling

/profile {  % label proc -> time
  2 dict begin
    /proc exch def
    /label exch def

    vmstatus pop /startMem exch def
    usertime /startTime exch def

    proc exec

    usertime startTime sub /elapsed exch def
    vmstatus pop startMem sub /memDelta exch def

    label print
    (: ) print
    elapsed =string cvs print
    (ms, ) print
    memDelta =string cvs print
    ( bytes) print
    () print

    elapsed
  end
} def

% Usage
(Heavy operation) {
  % ... expensive code
} profile pop

1.8.3. Resource Leaks Detection

/detectLeaks {  % proc -> leaked
  vmstatus pop /before exch def

  exec

  vmstatus pop /after exch def
  after before sub

  dup 0 gt {
    (Warning: Possible leak of ) print
    =string cvs print
    ( bytes) print
  } {
    pop
  } ifelse
} def

% Usage
{
  % Suspected leaking code
  /temp 1000 array def
  % Forgot to clean up temp
} detectLeaks

1.9. Cleanup Patterns

1.9.1. RAII Pattern (Resource Acquisition Is Initialization)

/withResource {  % acquireProc releaseProc useProc -> -
  3 dict begin
    /use exch def
    /release exch def
    /acquire exch def

    acquire exec /resource exch def

    {
      resource use exec
    } stopped {
      resource release exec
      stop
    } {
      resource release exec
    } ifelse
  end
} def

% Usage
{
  (file.txt) (r) file  % Acquire
} {
  closefile  % Release
} {
  256 string readline pop  % Use
} withResource

1.9.2. Scoped Resources

/withScope {  % initProc mainProc cleanupProc -> -
  3 dict begin
    /cleanup exch def
    /main exch def
    /init exch def

    save /snap exch def

    init exec

    {
      main exec
    } stopped {
      cleanup exec
      snap restore
      stop
    } {
      cleanup exec
      snap restore
    } ifelse
  end
} def

1.9.3. Finalizers

/Finalizer {
  <<
    /resources []
    /finalizers <<>>

    /register {  % resource finalizerProc -> -
      2 dict begin
        /finalizer exch def
        /resource exch def

        /resources [ resources aload pop resource ] def
        finalizers resource finalizer put
      end
    } bind

    /finalize {
      resources {
        /resource exch def
        finalizers resource known {
          finalizers resource get
          resource exch exec
        } if
      } forall

      /resources [] def
      /finalizers <<>> def
    } bind
  >>
} def

% Usage
Finalizer /fin exch def
(file.txt) (r) file
dup { closefile } fin /register get exec
% ... use file
fin /finalize get exec

1.10. Best Practices

1.10.1. Minimize Global State

% Good: local scope
{
  save
  % Local allocations
  /tempData 1000 array def
  % Process
  restore
} exec

% Bad: global pollution
/tempData 1000 array def
% Hard to clean up

1.10.2. Use Save/Restore for Temporary Data

% Good: automatic cleanup
/processLargeData {  % data -> result
  save exch

  % Create temporary working arrays
  /workspace 10000 array def
  /results 1000 array def

  % Process data
  % ... operations

  % Extract results before restore
  results 0 100 getinterval

  exch restore
} def

% Bad: manual cleanup
/processLargeData {
  /workspace 10000 array def
  /results 1000 array def
  % ... process
  % Forgot to clean up!
} def

1.10.3. Reuse Objects

% Good: object reuse
/pointBuffer 2 array def
0 1 99 {
  pop
  pointBuffer 0 rand put
  pointBuffer 1 rand put
  % Use pointBuffer
} for

% Bad: create new each time
0 1 99 {
  pop
  [rand rand]  % New array each iteration
} for

1.10.4. Cache Expensive Computations

% Good: cache results
/factorialCache 20 dict def

/factorial {  % n -> n!
  dup factorialCache exch known {
    factorialCache exch get
  } {
    dup 1 le {
      pop 1
    } {
      dup 1 sub factorial mul
    } ifelse
    dup factorialCache 3 1 roll put
  } ifelse
} def

% Bad: recalculate every time
/factorial {
  dup 1 le { pop 1 } { dup 1 sub factorial mul } ifelse
} def

1.11. Common Pitfalls

1.11.1. Memory Leaks

% Wrong: accumulating references
/data [] def
0 1 999 {
  /data [ data aload pop 1000 array ] def
  % data keeps growing!
} for

% Correct: use temporary scope
save
/data [] def
0 1 999 {
  /data [ data aload pop 1000 array ] def
} for
restore

1.11.2. Missing Cleanup

% Wrong: file not closed on error
(file.txt) (r) file /f exch def
f 256 string readline pop
% If error occurs, file stays open

% Correct: guaranteed cleanup
(file.txt) (r) file /f exch def
{
  f 256 string readline pop
} stopped {
  f closefile
  stop
} {
  f closefile
} ifelse

1.11.3. Stack Growth

% Wrong: unbalanced operations
/leakyProc {
  42  % Leaves extra value
  add
} def

0 1 999 {
  leakyProc
} for
% Stack has grown by 1000 elements!

% Correct: balanced
/cleanProc {
  add
} def

1.11.4. Dictionary Stack Overflow

% Wrong: repeated begin without end
0 1 99 {
  pop
  10 dict begin
  % ... forgot end
} for
% Dictionary stack overflow!

% Correct: balanced
0 1 99 {
  pop
  10 dict begin
    % ...
  end
} for

1.12. Performance Optimization

1.12.1. Batch Operations

% Good: batch allocation
save
[
  0 1 999 {
    1000 array
  } for
] /arrays exch def
% Process all
restore

% Bad: piecemeal
0 1 999 {
  pop
  save
  1000 array
  % Process
  restore
} for

1.12.2. Minimize VM Snapshots

% Good: one save for entire operation
save
% ... many operations
restore

% Bad: save per operation
0 1 99 {
  pop
  save
  % ... one operation
  restore
} for

1.12.3. Use Bind

% Good: bound procedure (faster, no dict lookups)
/fastProc {
  dup mul add
} bind def

% Slower: unbound (dict lookups each time)
/slowProc {
  dup mul add
} def

1.13. Resource Limits

1.13.1. Checking Limits

/checkResources {
  % Check VM
  vmstatus
  3 1 roll sub
  dup 10000 lt {
    (Warning: Low memory) print
  } if
  pop pop

  % Check stacks
  count 100 gt {
    (Warning: Deep operand stack) print
  } if

  countdictstack 20 gt {
    (Warning: Deep dictionary stack) print
  } if
} def

1.13.2. Enforcing Limits

/withMemoryLimit {  % maxBytes proc -> -
  2 dict begin
    /proc exch def
    /limit exch def

    vmstatus pop /start exch def

    {
      proc exec
      vmstatus pop start sub limit gt {
        (Memory limit exceeded) print
        stop
      } if
    } loop
  end
} def

1.14. Debugging Resource Issues

1.14.1. Memory Dump

/dumpMemory {
  (=== Memory Dump ===) print
  vmstatus
  (Maximum: ) print =string cvs print () print
  (Used: ) print =string cvs print () print
  (Level: ) print =string cvs print () print

  (Operand stack depth: ) print count =
  (Dictionary stack depth: ) print countdictstack =
} def

1.14.2. Track Allocations

/AllocationTracker {
  <<
    /allocations []

    /track {  % name size -> -
      realtime 3 array astore
      /allocations [ allocations aload pop 3 -1 roll ] def
    } bind

    /report {
      (Allocations:) print
      allocations {
        aload pop
        ( - ) print
        =string cvs print
        (: ) print
        =string cvs print
        ( bytes at ) print
        =string cvs print
        () print
      } forall
    } bind
  >>
} def

1.15. See Also


Back to top

Copyright © 2025 Ribose. PostScript is a trademark of Adobe. Distributed under the MIT License.