Following a disappointing attempt to achieve faster JSON processing (specifically parsing) with results, we finally reached near-parity with NSJSONSerialization in the latest instalment. This success was largely due to MPWSmallStringTable, which helped us unique strings before object creation – a step found to be surprisingly expensive, even for tagged pointer strings.
Streamlining with ObjectBuilder
In the previous part of this series, we demonstrated a relatively straightforward method for creating objects from plists generated by NSJSONSerialization.
Now, we introduce MPWObjectBuilder (.h .m), a subclass of MPWPlistBuilder with a few key distinctions: instead of generating dictionaries, it produces objects. Moreover, instead of using -setObject:forKey: to populate these dictionaries, it leverages the KVC message -setValue:forKey: to set values within the objects.
` ``` @implementation MPWObjectBuilder
-(instancetype)initWithClass:(Class)theClass { self=[super init]; self.cache=[MPWObjectCache cacheWithCapacity:20 class:theClass]; return self; }
-(void)beginDictionary { [self pushContainer:GETOBJECT(_cache) ]; }
-(void)writeObject:anObject forKey:aKey { [*tos setValue:anObject forKey:aKey]; }
``` `
That’s the core of it! The actual class implementation includes additional features not relevant to our current discussion. The _tos instance variable represents the top of the stack that MPWPlistBuilder utilizes during the result construction process. Meanwhile, MPWObjectCache functions as a factory for object creation.
Let’s put this into action and observe its capabilities:
` ``` -(void)decodeMPWDirect:(NSData*)json { NSArray *keys=@[ @“hi”, @“there”, @“comment”]; MPWMASONParser *parser=[MPWMASONParser parser]; MPWObjectBuilder builder=[[MPWObjectBuilder alloc] initWithClass:[TestClass class]]; [parser setBuilder:builder]; [parser setFrequentStrings:keys]; NSArray objResult = [parser parsedData:json]; NSLog(@“MPWMASON %@ with %ld elements”,[objResult firstObject],[objResult count]); }
``` `
While not the most elegant or comprehensive parser, it gets the job done.
Result: 621 ms.
This performance is not bad at all; only 50% slower than the baseline NSJSONSerialization when tested with our (not entirely representative) 44MB JSON file. Notably, it directly creates the final objects rather than just an intermediate representation. Furthermore, it’s approximately 7 times faster than Apple’s [JSONDecoder](https://developer.apple.com/documentation/foundation/jsondecoder).
Despite not yet reaching 100 MB/s, let alone our target of 2.5 GB/s, we are approaching a reasonable performance level given the context. Basic object creation consumes 140ms, while a mostly empty parse takes 124ms.
Looking Ahead: Analysis and Next Steps
Setting aside limitations such as its current applicability being restricted to relatively simple scenarios (e.g., an array containing a single object type), how can we further enhance this? Naturally, our focus is on speed. Let’s examine the profiling results:

As anticipated, the KVC code now emerges as the primary contributor, consuming around 40% of the total runtime. The locking functions grouped with -setValue:forKey: likely belong to its implementation. This minor time misattribution is a common occurrence in Instruments, something to keep in mind during analysis. While the cause might be missing frame-pointers (-fomit-frame-pointer), further investigation seems unnecessary as it doesn’t significantly impact our analysis.
This brings us to another crucial point: gather sufficient data to guide your next steps – no less, but importantly, no more. Both extremes are problematic, with the more prevalent one being premature optimization based on assumptions rather than data. Countless projects boast about high performance without any (comparative) benchmarking simply because they employed techniques perceived as fast. On the other hand, excessive benchmarking at every optimization stage, although less common, can hinder progress and has been observed in practice.
Therefore, next, our next objective is to eliminate KVC.
TOC
Somewhat Less Lethargic JSON Support for iOS/macOS, Part 1: The Status Quo
Somewhat Less Lethargic JSON Support for iOS/macOS, Part 2: Analysis
Somewhat Less Lethargic JSON Support for iOS/macOS, Part 3: Dematerialization
Equally Lethargic JSON Support for iOS/macOS, Part 4: Our Keys are Small but Legion
Less Lethargic JSON Support for iOS/macOS, Part 5: Cutting out the Middleman
Somewhat Faster JSON Support for iOS/macOS, Part 6: Cutting KVC out of the Loop
Faster JSON Support for iOS/macOS, Part 7: Polishing the Parser
Faster JSON Support for iOS/macOS, Part 8: Dematerialize All the Things!
Beyond Faster JSON Support for iOS/macOS, Part 9: CSV and SQLite
