#Regulation

Understanding Slot Access Costs in Common Lisp: Structures vs. Standard Instances

Tech Essays Reporter
4 min read

The post examines why accessing slots in CLOS instances is slower than in structures, explores the role of the Metaobject Protocol, presents benchmark code, and interprets results across several Lisp implementations.

Thesis

In Common Lisp the convenience of a fully dynamic object system (CLOS) comes with a measurable performance price: reading a slot from a standard instance is fundamentally more expensive than reading a slot from a plain structure. The difference stems from the need to verify class freshness, dispatch generic functions, and possibly re‑compile accessors, whereas a structure accessor can be inlined to a single memory reference.


Key Arguments

1. Memory layout and mutability

  • Structures are modeled as a two‑element tuple (CLASS SLOTS). The class part never changes, so the compiler may assume the slot vector’s layout is immutable and generate direct svref reads.
  • Standard instances carry an extra stamp (CLASS STAMP SLOTS). The stamp records the generation of the class definition; when a class is redefined the stamp changes, signalling that the slot vector may have been reshaped. Consequently every slot read must first confirm that the instance is up‑to‑date.

2. Generic function dispatch overhead

  • Instance readers are ordinary generic functions. Because methods can be added or overridden at runtime, the compiler cannot inline them safely. At call time the system must:
    1. Verify the instance’s stamp.
    2. Locate the appropriate method (dispatch on the class of the object).
    3. Possibly fall back to a slow path that recompiles the accessor if the class changed.
  • The slot-value function adds another layer: it looks up the slot definition in the class, then invokes the generic function slot-value-using-class. This extra indirection makes it the slowest of the three access patterns.

3. The MOP’s standard-instance-access

The Metaobject Protocol defines MOP:STANDARD-INSTANCE-ACCESS, an operation that directly indexes the slot vector after the stamp check. When declared inline it behaves much like a structure accessor, eliminating the generic‑function dispatch cost. Implementations that expose a fast variant (e.g., LispWorks’ FAST-STANDARD-INSTANCE-ACCESS) can achieve near‑structure performance.

4. Empirical comparison

The article provides a self‑contained benchmark that measures four access patterns on four Lisp implementations (CCL, ECL, LispWorks, SBCL):

  • Structure reader – pure svref access, inlined.
  • Instance reader – generic function call.
  • slot-value – generic function plus class‑slot lookup.
  • standard-instance-access – low‑level slot fetch.

The results are expressed as ratios relative to the fastest method in each implementation. Highlights:

  • SBCL and LispWorks show almost identical timings for structure and standard-instance-access, confirming the MOP’s effectiveness.
  • CCL’s instance access is roughly twice as slow as its structure access, indicating a less aggressive optimization.
  • ECL exhibits an extreme disparity (175× slower for instance access) because its structure readers are not inlined and its standard-instance-access is not as optimized.

Implications

  1. Performance‑critical code should prefer structures when the object’s layout is static. This is common in numeric kernels, parsers, or any tight loop where the overhead of generic dispatch dwarfs the actual computation.
  2. When mutability of the class is required (e.g., image‑based development, hot‑code swapping), the trade‑off is unavoidable. Developers can mitigate the cost by:
    • Using standard-instance-access directly instead of a generic reader.
    • Declaring readers inline where the implementation permits it.
    • Restricting redefinition of classes to initialization phases.
  3. Implementation choice matters. SBCL and LispWorks already expose fast instance access; CCL is close; ECL needs work. Projects that rely heavily on CLOS should benchmark on the target implementation before committing to a design.
  4. Future language extensions could provide a compile‑time hint that a class will never be redefined, allowing the compiler to generate structure‑like accessors for instances. This would give programmers a way to keep the flexibility of CLOS while regaining raw speed where needed.

Counter‑Perspectives

  • Some argue that the 2× slowdown on CCL is negligible compared to the benefits of dynamic class evolution. In many applications the cost of slot access is dwarfed by algorithmic work, so the extra flexibility outweighs raw speed.
  • Others point out that the benchmark isolates slot reads; real‑world code often performs additional work per read, making the relative difference less pronounced. Nevertheless, the benchmark remains a useful upper bound for the worst‑case overhead.
  • A third view suggests that the MOP’s standard-instance-access is an implementation detail that should not be relied upon in portable code. While true for strict portability, the specification does guarantee its existence, and most major implementations provide an inlineable variant, making it a pragmatic tool for performance‑sensitive libraries.

Closing Thoughts

The analysis underscores a core principle of Lisp’s object system: extensibility costs cycles. By understanding the underlying mechanics—stamps, generic dispatch, and the MOP’s low‑level primitives—developers can make informed decisions about when to embrace the full dynamism of CLOS and when to fall back on the leaner, immutable structure model. The benchmark data, while not exhaustive, offers a concrete illustration of these trade‑offs across the most widely used Lisp implementations, and points the way toward future optimizations that could narrow the gap without sacrificing the language’s hallmark flexibility.


Further Reading

  • The official CLOS specification (CLHS) – for a deep dive into generic functions and the MOP.
  • Pascal Costanza’s Closer‑MOP – a portable MOP library that abstracts over implementation differences.
  • Yukari Hafner’s trivial‑benchmark – a lightweight benchmarking framework useful for reproducing the measurements above.

Comments

Loading comments...