Difference Between Fast Classes and Fast Methods in Mulle-ObjC Runtime

Fast classes use a global 64-entry array to map frequently used class IDs directly to infraclass structures for O(1) class lookup, while fast methods use a per-class 24-entry table to cache common selector-to-implementation pointers for O(1) message dispatch.

The mulle-objc/mulle-objc-runtime implements two distinct compile-time optimization mechanisms that eliminate runtime overhead in Objective-C messaging. Both systems provide constant-time lookups but operate at different architectural levels: fast classes accelerate class metadata retrieval at the universe level, while fast methods streamline method dispatch within individual classes.

Fast Classes: Global Class Metadata Cache

Fast classes optimize the path from a class ID to its underlying struct _mulle_objc_infraclass. This mechanism benefits frequently instantiated classes such as NSString, NSArray, and NSNumber by bypassing the standard class cache lookup.

Structure and Storage Location

The fast class table resides in the universe (struct _mulle_objc_universe), making it a single global instance shared across the process. The table definition in src/mulle-objc-fastclasstable.h declares a fixed array of 64 atomic pointers:

#define MULLE_OBJC_S_FASTCLASSES   64

struct _mulle_objc_fastclasstable
{
    union _mulle_objc_atomicclasspointer_t   classes[ MULLE_OBJC_S_FASTCLASSES];
};

The lookup helper mulle_objc_fastclasstable_get_infraclass performs a single array read with no hashing or collision resolution:

static inline struct _mulle_objc_infraclass *
mulle_objc_fastclasstable_get_infraclass( struct _mulle_objc_fastclasstable *table,
                                          unsigned int i)
{
    return (struct _mulle_objc_infraclass *) _mulle_atomic_pointer_read(
                &table->classes[i].pointer);
}

Population at Startup

The runtime populates the fast class table during universe initialization. In src/mulle-objc-universe.c (lines 2198–2335), the function _mulle_objc_universe_set_fastclass inserts a class into the global table using an index generated by compile-time macros MULLE_OBJC_FASTCLASSHASH_n. Because the table is modified only at startup, subsequent accesses are lock-free and read-only.

Fast Methods: Per-Class Dispatch Optimization

Fast methods target the message send bottleneck by caching the most frequently invoked selectors—such as alloc, init, retain, and autorelease—directly within each class structure.

Embedded Virtual Table

Unlike the global fast class table, every class contains its own fast method table (vtab) declared in src/mulle-objc-fastmethodtable.h:

#define MULLE_OBJC_S_FASTMETHODS    24

struct _mulle_objc_fastmethodtable
{
    union _mulle_objc_atomicmethodpointer_t   methods[ MULLE_OBJC_S_FASTMETHODS];
};

This 24-entry array stores raw function pointers indexed by selector ID, allowing mulle_objc_msgSend to bypass the normal method list traversal and superclass walk.

Lazy Resolution and Fault Handling

During class initialization (_mulle_objc_class_init → _mulle_objc_fastmethodtable_init in src/mulle-objc-class.c, lines 182–197), each slot is initially filled with a fault handler. The first time a fast selector is dispatched, the fault handler atomically swaps the placeholder with the resolved implementation pointer using a compare-and-swap operation. After this single lazy initialization, the slot remains read-only for the class lifetime.

The dispatch logic in src/mulle-objc-call.h (lines 245–300) checks the fast method index before falling back to standard lookup:

int index = mulle_objc_get_fastmethodtable_index(methodid);
if( index >= 0 )
    return _mulle_objc_fastmethodtable_invoke(obj, methodid, parameter,
                                              &cls->vtab, index);
/* otherwise fall back to the normal method list / cache */

Architectural Comparison

Characteristic Fast Classes Fast Methods
Scope Global universe table Per-class embedded table
Entry Count 64 (MULLE_OBJC_S_FASTCLASSES) 24 (MULLE_OBJC_S_FASTMETHODS)
Cached Data Class ID → Infraclass pointer Selector ID → Implementation pointer
Configuration MULLE_OBJC_FASTCLASSHASH_n macros MULLE_OBJC_FASTMETHODHASH_n macros
Initialization Startup-time population Lazy fault-handler resolution
Thread Safety Read-only after startup Atomic CAS on first access

Practical Interaction in Message Sending

When code allocates a fast-class object like NSString, both optimizations activate sequentially:

  1. The runtime retrieves the NSString infraclass via mulle_objc_fastclasstable_get_infraclass using the pre-computed class ID index.
  2. The message send invokes alloc via _mulle_objc_fastmethodtable_invoke using slot index 0 (MULLE_OBJC_ALLOC_METHODID).
  3. The subsequent init call hits slot index 1 (MULLE_OBJC_INIT_METHODID) in the same per-class table.
/* Allocate an NSString – both fast paths are used */
mulle_objc_object_t *obj = mulle_objc_msgSend( NULL,
                                               MULLE_OBJC_ALLOC_METHODID,
                                               NULL );
obj = mulle_objc_msgSend( obj,
                          MULLE_OBJC_INIT_METHODID,
                          NULL );

Configuration and Limitations

Both mechanisms require compile-time registration. The runtime reserves slots only for classes and selectors explicitly hashed via MULLE_OBJC_FASTCLASSHASH_n and MULLE_OBJC_FASTMETHODHASH_n definitions.

  • Fast classes are limited to the first 64 classes registered; additional classes fall back to the standard class cache.
  • Fast methods support only 24 selectors per class; selectors outside this set use the conventional method list lookup.

Because these tables are fixed-size arrays rather than hash maps, they provide guaranteed O(1) access with no collision handling overhead.

Summary

  • Fast classes store infraclass pointers in a global 64-entry universe table, eliminating class lookup overhead for commonly instantiated classes.
  • Fast methods cache implementation pointers in per-class 24-entry virtual tables, bypassing selector resolution for high-frequency messages like allocation and initialization.
  • Both systems use compile-time hash macros (MULLE_OBJC_FASTCLASSHASH_n and MULLE_OBJC_FASTMETHODHASH_n) to determine array indices.
  • Fast methods employ lazy fault-handling with atomic compare-and-swap initialization, while fast classes are populated strictly at startup.
  • These optimizations work together in src/mulle-objc-call.h to provide constant-time dispatch paths for critical object lifecycle operations.

Frequently Asked Questions

How many entries do the fast tables support?

The fast class table supports 64 entries defined by MULLE_OBJC_S_FASTCLASSES in src/mulle-objc-fastclasstable.h, while the fast method table supports 24 entries defined by MULLE_OBJC_S_FASTMETHODS in src/mulle-objc-fastmethodtable.h. These are fixed limits set at compile time.

Can developers add custom classes or methods to the fast tables?

Yes, but it requires recompiling the runtime with additional MULLE_OBJC_FASTCLASSHASH_n or MULLE_OBJC_FASTMETHODHASH_n macro definitions. The runtime does not support dynamic registration of fast slots at startup; the mappings are generated statically to ensure constant-time array indexing.

Are fast classes and fast methods thread-safe?

Both mechanisms are thread-safe through immutability. The fast class table is populated during universe startup and becomes read-only before any thread accesses it. Fast method slots use an atomic compare-and-swap operation only during the first lazy resolution; thereafter, the slots are read-only, allowing lock-free concurrent message sending.

What happens when a fast table fills up?

Once the 64 fast class slots or 24 fast method slots are exhausted, the runtime silently falls back to standard lookup mechanisms. For classes, this means using the class cache; for methods, this triggers the full selector-to-implementation resolution path via method list traversal and superclass walking.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →