11
$\begingroup$

NSZone is a type representing a memory allocation. NSObject implements the allocWithZone: method to allocate memory in a specific zone, or the default zone if none is provided; alloc is equivalent to allocWithZone:nil.

NSZone was originally created to allow custom allocation and deallocation:

If you are mass-allocating hundreds of cheap objects, you may find the cost of actually allocating space for them becomes significant. Because the standard zone is used all the time, it can become very patchy; deleted objects can leave awkward gaps throughout memory. The allocator for the standard NSZone knows this, and it tries to fill these gaps in preference to grabbing more memory off the system, but this can be costly in time if the zone has grown quite large.

If you want to mass-allocate objects, then, you can create your own zone and tell it not to bother with finding gaps to put new objects in. The allocator can now jump to the end of its allotted memory each time and quickly assign memory to your new objects, saving a lot of effort.

Rumor has it that NSZone could save you deallocation time in the Good Old Days, too, with a method that simply chucks away all the allotted memory without bothering to call deallocators.

(source)

However, in modern programs, the zone is always nil. From the archived Objective-C documentation:

  • You cannot use memory zones.
    There is no need to use NSZone any more—they are ignored by the modern Objective-C runtime anyway.

Custom allocators have been used in other languages as well, such as by the C++ STL. Given the potential benefits to custom allocation, why was NSZone deprecated? And why do (most) modern languages provide no equivalent?

$\endgroup$

2 Answers 2

6
$\begingroup$

Someone thought it was a good idea, maybe it even was a good idea 30 years ago, and now it isn't anymore. That happens.

In modern Objective-C, you just allocate memory, and it is either allocated until it is released, or the allocation failed. In old Objective-C, the developer could create heap zones and allocate memory within a heap zone. I think it was possible to destroy a complete heap zone and then all allocations within that heap zone would be gone, which to me today with modern experience seems a very, very bad idea.

One idea was that allocating memory in zones could provide better performance. Since then, memory allocation performance has been improved through other means. For example, by using a very simple allocator ("simple" in the sense that often very little work needs to be done to allocate or deallocate memory) that doesn't use any memory near the allocated memory for bookkeeping, allocations that are aware of threads, so that the allocator knows "this was allocated in thread X and was never seen by anyone other than thread X, so it can be deallocated without any communication with other threads", and many other things.

Zones were an attempt at optimisation at the expense of the developer, who either didn't care and didn't get any optimisation, or who cared and spend lots of time and code on performance instead of functionality. It is a much better strategy to leave this kind of optimisation to one library developer who knows everything about memory allocation, so that a million developers don't need to care about it.

$\endgroup$
1
  • 1
    $\begingroup$ Also, you can get a new zone from the OS by starting a new process, but for that to be viable, we needed cheap context switches and IPC first. $\endgroup$ Jul 26, 2023 at 1:51
5
$\begingroup$

Something else which should be mentioned is that Objective-C objects and methods suffer from overhead, because objects are reference counted and methods are dynamic (every call is a lookup) among other things. This overhead is such a big problem it was one of the reasons Swift was developed, and Apple themselves claim Swift is up to 2.6x faster, in part because its methods are static.

So, if you really need your app to be performant, you're better off not using Objective-C objects at all, instead resorting to C structs or Objective-C++ classes (in the olden days when NSZone was deprecated, or Swift structs/classes now).

In C/C++ using your own allocator is easy. In C you just replace malloc and free with your custom allocator's equivalent, like MyMalloc/MyFree in this lab assignment and context_alloc in tscoding/arena. In C++ you allocate storage via above, then use placement new to construct a class instance (run its constructor) within this storage (and operator delete to run the destructor before freeing).

What about Swift? Honestly I'm not sure, since custom allocators are currently not possible unless you want to use unsafe bitcasts. In this case you probably also resort to C/C++ code, since although Swift methods are static, Swift classes and even structs are still reference-counted and thus have extra overhead.


In the extreme case, instead of calling [[MyObjectiveCClass alloc] init], you could cast arbitrary preallocated bytes to your class and then call init on that; or in Swift, you could initialize preallocated bytes if necessary and then unsafeBitCast(_:to:) them to your instance's type. However I'm not sure how this interacts with ARC and other features of the Objective-C/Swift runtime, and I doubt it's defined or stable (could break in future versions of the OS). More likely, if you have very performant code and still need to use Objective-C or Swift objects, you can allocate them all in advance and just avoid allocating in your code's hot path.

$\endgroup$
1
  • 1
    $\begingroup$ Custom Swift allocations are absolutely possible without unsafe bitcasts if you restrict yourself to structs instead of classes — you can use the method bindMemory to convert an untyped pointer to a typed pointer. $\endgroup$
    – Bbrk24
    Jul 26, 2023 at 12:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .