Home > Archive > Objective C > November 2005 > Re: How to speed method selector in category
You are viewing an archived Text-only version of the thread.
To view this thread in it's original format and/or if you want to reply to
this thread please [click here]
| Author |
Re: How to speed method selector in category
|
|
| Michael Ash 2005-11-28, 7:59 am |
| Michael Hopkins <michael.hopkins@hopkins-research.com> wrote:
>
>
> Hi all
>
> I want to use NSArray's from 1 to n with this category:
>
>
> @interface NSArray ( UnitOffset )
>
> - (id) at:(unsigned) i; // returns object at i (1 <= i <= count)
>
> - (unsigned) indexOf:(id) o; // which i (1 <= i <= count) holds o
>
> @end
>
>
> It's a Foundation class in this case but I think the question relates to all
> Objective-C code that uses categories. Here is the straightforward
> implementation.
>
>
> @implementation NSArray ( UnitOffset )
>
> - (id) at:(unsigned) i { // implementation (1)
> return [ self objectAtIndex: --i ];
> }
>
> - (unsigned) indexOf:(id) o {
> return [ self indexOfObject: o ] + 1;
> }
>
> @end
>
>
> This works fine but requires an extra indirection in each case and I don't
> think the compiler or runtime can be smart enough to avoid this.
It most certainly can't, because you don't know what the target is.
> Obviously a
> way to deal with this is to bypass the runtime with a direct function
> pointer call. This is an example for objectAtIndex:
>
>
> // setup code
> SEL oai_sel = @selector( objectAtIndex: );
> typedef id (*OAI_fp) ( id, SEL, unsigned );
> OAI_fp oai_fp = (OAI_fp) [ NSArray instanceMethodForSelector: oai_sel ];
>
>
> Giving this alternative implementation to (1):
>
>
> - (id) at:(unsigned) i { // implementation (2)
> return oai_fp( self, oai_sel, --i );
> }
And if you sublass your class and override objectAtIndex:, you've
conveniently bypassed the overridden code. Oops!
This is called IMP caching, and it's dangerous, for exactly this reason.
You're taking ObjC's natural dynamic dispatch and shoving it back into a
static model. If the code is not truly static in nature, bad things
happen.
This isn't just whiny theoretical nonsense either. Your code as written
simply *will not work*, even if you fix the error. This is because NSArray
is an abstract class, and the actual implementation is handled by private
subclasses. The function pointer you get to NSArray's objectAtIndex: will
simply throw an error when called, because it's one of the primitive
methods that NSArray requires subclasses to override.
> This gives me the following compile error in lines 1 & 3 of the setup code:
>
> error: initializer element is not constant
>
> Is this because the setup code is not within the implementation of the
> NSArray class and, if so, is this idea therefore not possible using
> categories? Alternatively, have I maybe messed up somewhere else or do I
> just have to create a subclass of NSArray to get what I want?
It means exactly what it says: the initializer is not constant. In C,
global variables can only be initialized with constant expressions. For
example: int x = 0; You can't use non-constant expressions, like: int x =
sin(1.2);
C++ allows non-constant expressions, but even with ObjC++, you can't use
ObjC expressions in global initializers, because they usually get run
before the ObjC runtime or Cocoa are set up.
The solution here is to initialize your globals from a function or method
that gets executed early. The only way I know of to do this automatically
(as opposed to simply writing a +setupStuff class method) is to implement
+ (void)load in your category. This will be executed when your category
loads, which is after NSArray itself loads, and you can initialize things
from there.
See:
http://developer.apple.com/document...do-in-_002bload
For a list of what you can do in +load, it's pretty restrictive.
But as noted above, while this will fix your error, it still won't give
you working code.
--
Michael Ash
Rogue Amoeba Software
| |
| Michael Hopkins 2005-11-29, 7:06 pm |
| On 28/11/05 11:44, in article 1133178296.249980@nfs-db1.segnet.com, "Michael
Ash" <mike@mikeash.com> wrote:
> Michael Hopkins <michael.hopkins@hopkins-research.com> wrote:
>
> It most certainly can't, because you don't know what the target is.
>
>
> And if you sublass your class and override objectAtIndex:, you've
> conveniently bypassed the overridden code. Oops!
>
> This is called IMP caching, and it's dangerous, for exactly this reason.
> You're taking ObjC's natural dynamic dispatch and shoving it back into a
> static model. If the code is not truly static in nature, bad things
> happen.
>
> This isn't just whiny theoretical nonsense either. Your code as written
> simply *will not work*, even if you fix the error. This is because NSArray
> is an abstract class, and the actual implementation is handled by private
> subclasses. The function pointer you get to NSArray's objectAtIndex: will
> simply throw an error when called, because it's one of the primitive
> methods that NSArray requires subclasses to override.
>
>
> It means exactly what it says: the initializer is not constant. In C,
> global variables can only be initialized with constant expressions. For
> example: int x = 0; You can't use non-constant expressions, like: int x =
> sin(1.2);
>
> C++ allows non-constant expressions, but even with ObjC++, you can't use
> ObjC expressions in global initializers, because they usually get run
> before the ObjC runtime or Cocoa are set up.
>
> The solution here is to initialize your globals from a function or method
> that gets executed early. The only way I know of to do this automatically
> (as opposed to simply writing a +setupStuff class method) is to implement
> + (void)load in your category. This will be executed when your category
> loads, which is after NSArray itself loads, and you can initialize things
> from there.
>
> See:
>
> http://developer.apple.com/document....1/gcc/What-you
> -can-and-what-you-cannot-do-in-_002bload.html#What-you-can-and-what-you-cannot
> -do-in-_002bload
>
> For a list of what you can do in +load, it's pretty restrictive.
>
> But as noted above, while this will fix your error, it still won't give
> you working code.
Thanks for the input Michael. Looks like I am onto a loser with this idea.
Do you have any suggestions for how I can do what I am aiming at here
efficiently and safely?
M
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
_/ _/ _/_/_/ Hopkins Research Ltd
_/ _/ _/ _/
_/_/_/_/ _/_/_/ http://www.hopkins-research.com/
_/ _/ _/ _/
_/ _/ _/ _/ 'touch the future'
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
| |
| Sherm Pendley 2005-11-29, 7:06 pm |
| Michael Hopkins <michael.hopkins@hopkins-research.com> writes:
> Do you have any suggestions for how I can do what I am aiming at here
> efficiently and safely?
Have you profiled your code to determine if the overhead of the additional
message is even significant?
Especially in the second case - indexOfObject: has to perform a sequential
search of the array, comparing each object in it with the target object.
The addition of a single message will be utterly buried by the cost of the
actual search.
Most often, algorithmic improvements (replacing a sequential search with a
binary search, for instance) are preferable over "micro optimizations" such
as IMP cacheing.
sherm--
--
Cocoa programming in Perl: http://camelbones.sourceforge.net
Hire me! My resume: http://www.dot-app.org
| |
| Michael Ash 2005-11-29, 7:06 pm |
| Michael Hopkins <michael.hopkins@hopkins-research.com> wrote:
>
> Thanks for the input Michael. Looks like I am onto a loser with this idea.
>
> Do you have any suggestions for how I can do what I am aiming at here
> efficiently and safely?
The first rule of Optimization Club is, you do not talk about... oops,
sorry, wrong rules.
The First Rule of Program Optimization: Don't do it.
The Second Rule of Program Optimization (for experts only!): Don't do it
yet.
In other words, implement your methods as you have done, and then forget
about things. Objective-C message sending overhead is not that high,
particularly considering what it does, but even when the extra capability
is less than necessary it's fast. It typically takes 50 CPU cycles, which
on my machine (1.5GHZ) is around 30 nanoseconds. If you should ever get to
the point where an extra 30 nanoseconds of call overhead is making your
stuff slow, then you could consider drastic action to improve the
situation, but I doubt you'll ever get to that point.
--
Michael Ash
Rogue Amoeba Software
|
|
|
|
|