Custom Allocator and 64-bit pointers, ARC

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Custom Allocator and 64-bit pointers, ARC

Tom Sheffler
I have an application that models events as objects.  They are frequently allocated and deallocated.  It occurred to me that this class might be a canidate for a custom allocator that allocates objects from a simple pool.

I found this article (from 2010)
  https://www.mikeash.com/pyblog/friday-qa-2010-12-17-custom-object-allocators-in-objective-c.html
that describes my situation.

  - objects allocated and deallocated on same thread
  - not subclassed
  - direct subclass of NSObject

The gist of it is to override allocWithZone: to retrieve objects from a 'cache' (it could be pre-allocated, in my application).  There is a line that sets the ISA Pointer of the new object to the class.

I looked through the source code to NSObject.m and libobjc2/runtime.c and it looks like his idea doesn't violate any assumptions.

But I've been wondering a couple of things about this in the context of ARC and a 64-bit implementation.

    + (id)allocWithZone: (NSZone *)zone
    {
        id obj = GetObjectFromCache();
        if(obj)
            *(Class *)obj = self;   // set the ISA pointer to the CLASS
        else
            obj = [super allocWithZone: zone];
        return obj;
    }


1.  Is the assignment of the ISA pointer (the first slot in the object) to the CLASS
    still valid in 2018?  With 64-bit pointers I think some bits are used for refcounts
    and other things.  Maybe this assignment isn't safe.

    This article
        https://sectionfive.net/blog/2015/03/31/arc-in-depth-part-i/

    says that with "tagged pointers" you need to use a function object_setClass().

    Is that the right thing for libobjc2 and GNUstep.    

2.  Will this work with ARC?  It seems like keeping reference counts of references to instances
    is independent of alloc/dealloc of objects, so it shouldn't matter.


Thanks for any information or pointers!

-Tom



_______________________________________________
Discuss-gnustep mailing list
[hidden email]
https://lists.gnu.org/mailman/listinfo/discuss-gnustep
Reply | Threaded
Open this post in threaded view
|

Re: Custom Allocator and 64-bit pointers, ARC

David Chisnall-7
On 15 Oct 2018, at 17:12, Tom Sheffler <[hidden email]> wrote:

>
> I have an application that models events as objects.  They are frequently allocated and deallocated.  It occurred to me that this class might be a canidate for a custom allocator that allocates objects from a simple pool.
>
> I found this article (from 2010)
>  https://www.mikeash.com/pyblog/friday-qa-2010-12-17-custom-object-allocators-in-objective-c.html
> that describes my situation.
>
>  - objects allocated and deallocated on same thread
>  - not subclassed
>  - direct subclass of NSObject
>
> The gist of it is to override allocWithZone: to retrieve objects from a 'cache' (it could be pre-allocated, in my application).  There is a line that sets the ISA Pointer of the new object to the class.

It’s worth making sure you can turn this off.  90% of the time that I’ve benchmarked code that uses a custom allocator, it runs faster if you disable it because of fragmentation and / or interactions with threading.  Often this is code that *did* run faster with the custom allocator when it was written, but CPUs and memory allocators became a lot faster over the years.

> I looked through the source code to NSObject.m and libobjc2/runtime.c and it looks like his idea doesn't violate any assumptions.
>
> But I've been wondering a couple of things about this in the context of ARC and a 64-bit implementation.
>
>    + (id)allocWithZone: (NSZone *)zone
>    {
>        id obj = GetObjectFromCache();
>        if(obj)
>            *(Class *)obj = self;   // set the ISA pointer to the CLASS
>        else
>            obj = [super allocWithZone: zone];
>        return obj;
>    }

You might want to put something in +initialize to store self in a static and then check that self == {cached self}, falling back to the superclass implementation.  Note that now your code is about as complex as the fast path of a modern memory allocator, so it’s unclear that it would actually make things faster (unless you’re on GNU/Linux, because pretty much anything outperforms glibc’s default malloc implementation).

>
>
> 1.  Is the assignment of the ISA pointer (the first slot in the object) to the CLASS
>    still valid in 2018?  With 64-bit pointers I think some bits are used for refcounts
>    and other things.  Maybe this assignment isn't safe.
>
>    This article
>        https://sectionfive.net/blog/2015/03/31/arc-in-depth-part-i/
>
>    says that with "tagged pointers" you need to use a function object_setClass().
>
>    Is that the right thing for libobjc2 and GNUstep.    

Please use object_setClass().  This is portable and will work in the future (and will prevent clang from shouting at you).  Direct assignment does currently work, but we make no guarantees that it will continue to do so.

>
> 2.  Will this work with ARC?  It seems like keeping reference counts of references to instances
>    is independent of alloc/dealloc of objects, so it shouldn't matter.

Maintaining the cache is difficult in ARC, because ARC does not allow resurrection.  Your code will need to be compiled without ARC and to implement a dealloc that doesn’t call [super dealloc].  This means that you’ll lose out on the fast paths for deallocation.  You’ll also have some odd interactions with weak pointers.  I’d have to check the code to see exactly what the failure modes will be.  I think there’s now an attribute you can stick on a class to say that it doesn’t support weak pointers - you’ll probably want to do that.

Oh, and do profile your code!

David


_______________________________________________
Discuss-gnustep mailing list
[hidden email]
https://lists.gnu.org/mailman/listinfo/discuss-gnustep
Reply | Threaded
Open this post in threaded view
|

Re: Custom Allocator and 64-bit pointers, ARC

Tom Sheffler
Hi David -

Thanks for all of that information.  (FYI - I am targeting clang/linux).  I suspect I will end up not pursuing this path in the end, but might make a little
detour and run some experiments with profiling.  The ARC interactions were surprising - thanks for that note.

-Tom

> On Oct 15, 2018, at 9:57 AM, David Chisnall <[hidden email]> wrote:
>
> On 15 Oct 2018, at 17:12, Tom Sheffler <[hidden email]> wrote:
>>
>> I have an application that models events as objects.  They are frequently allocated and deallocated.  It occurred to me that this class might be a canidate for a custom allocator that allocates objects from a simple pool.
>>
>> I found this article (from 2010)
>> https://www.mikeash.com/pyblog/friday-qa-2010-12-17-custom-object-allocators-in-objective-c.html
>> that describes my situation.
>>
>> - objects allocated and deallocated on same thread
>> - not subclassed
>> - direct subclass of NSObject
>>
>> The gist of it is to override allocWithZone: to retrieve objects from a 'cache' (it could be pre-allocated, in my application).  There is a line that sets the ISA Pointer of the new object to the class.
>
> It’s worth making sure you can turn this off.  90% of the time that I’ve benchmarked code that uses a custom allocator, it runs faster if you disable it because of fragmentation and / or interactions with threading.  Often this is code that *did* run faster with the custom allocator when it was written, but CPUs and memory allocators became a lot faster over the years.
>
>> I looked through the source code to NSObject.m and libobjc2/runtime.c and it looks like his idea doesn't violate any assumptions.
>>
>> But I've been wondering a couple of things about this in the context of ARC and a 64-bit implementation.
>>
>>   + (id)allocWithZone: (NSZone *)zone
>>   {
>>       id obj = GetObjectFromCache();
>>       if(obj)
>>           *(Class *)obj = self;   // set the ISA pointer to the CLASS
>>       else
>>           obj = [super allocWithZone: zone];
>>       return obj;
>>   }
>
> You might want to put something in +initialize to store self in a static and then check that self == {cached self}, falling back to the superclass implementation.  Note that now your code is about as complex as the fast path of a modern memory allocator, so it’s unclear that it would actually make things faster (unless you’re on GNU/Linux, because pretty much anything outperforms glibc’s default malloc implementation).
>
>>
>>
>> 1.  Is the assignment of the ISA pointer (the first slot in the object) to the CLASS
>>   still valid in 2018?  With 64-bit pointers I think some bits are used for refcounts
>>   and other things.  Maybe this assignment isn't safe.
>>
>>   This article
>>       https://sectionfive.net/blog/2015/03/31/arc-in-depth-part-i/
>>
>>   says that with "tagged pointers" you need to use a function object_setClass().
>>
>>   Is that the right thing for libobjc2 and GNUstep.    
>
> Please use object_setClass().  This is portable and will work in the future (and will prevent clang from shouting at you).  Direct assignment does currently work, but we make no guarantees that it will continue to do so.
>
>>
>> 2.  Will this work with ARC?  It seems like keeping reference counts of references to instances
>>   is independent of alloc/dealloc of objects, so it shouldn't matter.
>
> Maintaining the cache is difficult in ARC, because ARC does not allow resurrection.  Your code will need to be compiled without ARC and to implement a dealloc that doesn’t call [super dealloc].  This means that you’ll lose out on the fast paths for deallocation.  You’ll also have some odd interactions with weak pointers.  I’d have to check the code to see exactly what the failure modes will be.  I think there’s now an attribute you can stick on a class to say that it doesn’t support weak pointers - you’ll probably want to do that.
>
> Oh, and do profile your code!
>
> David
>


_______________________________________________
Discuss-gnustep mailing list
[hidden email]
https://lists.gnu.org/mailman/listinfo/discuss-gnustep