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

> Explore the difference between fast classes and fast methods in mulle-objc runtime. Learn how O(1) class lookup and O(1) message dispatch optimize performance.

- Repository: [mulle-objc/mulle-objc-runtime](https://github.com/mulle-objc/mulle-objc-runtime)
- Tags: deep-dive
- Published: 2026-03-07

---

**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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-fastclasstable.h) declares a fixed array of 64 atomic pointers:

```c
#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:

```c
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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-fastmethodtable.h):

```c
#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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/src/mulle-objc-call.h) (lines 245–300) checks the fast method index before falling back to standard lookup:

```c
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.

```c
/* 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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/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`](https://github.com/mulle-objc/mulle-objc-runtime/blob/main/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.