NSMutableArray + NSEnumerator = No Memory

  • Greetings,

    I've been tracking down a weird crash that only occurs for a
    user in Germany.

    I was eventually able to isolate the problem; My application is
    running out of memory. The crash occurs at

        NSMutableArray* files;
        ...
        NSEnumerator* e = [files objectEnumerator];  <-- crashes in
    CFArrayGetValues > __memcpy

    It appears that -[NSArray objectEnumerator] is first making a
    copy of the entire array!!!

    I never noticed this before, because the collection were
    reasonably small (5-100 items), but this user had 30,000 items.
    The enumerator is used to search for a match (the match is
    complicated, so isEquals/containsObject is not an option). This
    will happen (you guessed it!) 30,000 times -- my application is
    trying to allocate 900,000,000 temporary object pointers (30,000
    x 30,000) and naturally runs out of memory.

    I tried to write some test cases in order to reproduce this. I
    turns out that this only seems to occur if other NSEnumerator
    objects have been returned by this collection, and those
    enumerators haven't finished enumerating through the entire
    collection. Of course, since I'm using an enumerator to search
    the collection for a match, this it the normal case.

    I'm not happy with this development, and can't imagine why
    NSArray/NSEnumerator would be doing this, but clearly I need to
    develop some work around. (Not to mention that my faith in the
    efficiency of the core Cocoa classes has been eroded.) I'll file
    a bug report, but I clearly need a workaround.

    Has anyone else run into this and is there a simple way to avoid
    this problem? My options appear to be:

    1 - Use objectAtIndex: to iterate through the array.

    2 - Create an auto release pool and discard the NSEnumerator
    during every search.

    3 - Abandon NSArray and roll my own collection.

    I'm leaning towards (1) because it minimizes the code change and
    shouldn't incur too much additional overhead. (2) will keep me
    from running out of memory, but doesn't guarantee that other
    calls to objectEnumerator won't make gratuitous copies of the array.

    (3) is ultimately the best solution, as I can increase the speed
    and reduce the memory footprint of the collection considerably
    by dropping NSArray altogether. But that's a lot of coding and
    I'd rather not make any radical changes in my application at the moment.

    --
    James Bucanek
  • On Sep 22, 2007, at 11:36 AM, James Bucanek wrote:
    > 1 - Use objectAtIndex: to iterate through the array.
    >
    > 2 - Create an auto release pool and discard the NSEnumerator during
    > every search.

    This is likely a good idea to do regardless of the rest of the problem
    as it is likely that you are causing, directly or indirectly,
    temporary objects to be created.  Dumping 'em every so often when
    doing lots of looping is generally a good idea.

    > 3 - Abandon NSArray and roll my own collection.
    >
    > I'm leaning towards (1) because it minimizes the code change and
    > shouldn't incur too much additional overhead. (2) will keep me from
    > running out of memory, but doesn't guarantee that other calls to
    > objectEnumerator won't make gratuitous copies of the array.
    >
    > (3) is ultimately the best solution, as I can increase the speed and
    > reduce the memory footprint of the collection considerably by
    > dropping NSArray altogether. But that's a lot of coding and I'd
    > rather not make any radical changes in my application at the moment.

    Are you sure that it is actually making a copy of the objects and
    that, given the number of iterations, you are simply exhausting memory
    by filling up the autorelease pool with temporary objects that are
    created as a part of your matching algorithm?

    I would be surprised if the implementation of NSArray has any notion
    of a previously allocated NSEnumerator still being active on the
    array.    Mutation of the underlying array is always unsafe during
    iteration and, thus, doing so is unlikely to add any safety during
    execution.

    (3) sounds fairly futile.  NSArray and friends have been quite highly
    optimized over time.  Certainly, there are likely more optimizations
    possible, but they are unlikely to be simple to implement.  Unless
    you have a highly specific usage pattern for which a non-general
    algorithm will be a significant performance win, it is unlikely that
    re-engineering that which already exists in the Foundation is going to
    be much of a win.

    I'm not saying it isn't possible that your own custom collection
    wouldn't be a win, but it sounds like you still have some
    investigation to do in regards to memory usage before going there.

    b.bum
  • On 22/09/2007, James Bucanek <subscriber...> wrote:

    > I never noticed this before, because the collection were
    > reasonably small (5-100 items), but this user had 30,000 items.
    > The enumerator is used to search for a match (the match is
    > complicated, so isEquals/containsObject is not an option). This
    > will happen (you guessed it!) 30,000 times -- my application is
    > trying to allocate 900,000,000 temporary object pointers (30,000
    > x 30,000) and naturally runs out of memory.

    900,000,000 * 4 = 3,600,000,000 BYTES!!! MacOS X per application
    memory limit = 4GB (last time I checked), that's with all the UI
    candy, ObjC baggage, etc!  Can you not process them a small bunch at a
    time?

    --
    Igor M.
  • On 22/09/2007, Igor Mozolevsky <igor...> wrote:
    >
    > 900,000,000 * 4 = 3,600,000,000 BYTES!!! MacOS X per application
    > memory limit = 4GB (last time I checked), that's with all the UI
    > candy, ObjC baggage, etc!  Can you not process them a small bunch at a
    > time?
    >
    > --
    > Igor M.

    Hmm... Seems I pressed send instead of save... Anyway, as I was
    saying, do you actually need all of them in memory? The way I deal
    with loading in large files before doing anything with them is that I
    read a chunk in, process that, then read th next chunk into the same
    memory space... Can't really comment any further as you've not really
    said how the collection is structured or why you need N*N elements....

    --
    Igor M.
  • Bill Bumgarner <mailto:<bbum...> wrote (Saturday, September
    22, 2007 12:05 PM -0700):

    > On Sep 22, 2007, at 11:36 AM, James Bucanek wrote:
    >> 1 - Use objectAtIndex: to iterate through the array.
    >>
    >> 2 - Create an auto release pool and discard the NSEnumerator
    >> during every search.
    >
    > This is likely a good idea to do regardless of the rest of the
    > problem as it is likely that you are causing, directly or
    > indirectly, temporary objects to be created.  Dumping 'em
    > every so often when doing lots of looping is generally a good idea.

    I was doing that already (once per collection), but not
    aggressively enough to avoid this problem. In fact, if the
    NSEnumerator wasn't making whole copies of the entire array
    every time a new enumerator was created, there wouldn't be any
    problem at all. During the search loop, I really only expected
    one object (the enumerator) to be created, so at most there
    would have been N objects created per auto release pool.

    >> 3 - Abandon NSArray and roll my own collection.
    >>
    >> I'm leaning towards (1) because it minimizes the code change
    >> and shouldn't incur too much additional overhead. (2) will
    >> keep me from running out of memory, but doesn't guarantee that
    >> other calls to objectEnumerator won't make gratuitous copies
    >> of the array.
    >>
    >> (3) is ultimately the best solution, as I can increase the
    >> speed and reduce the memory footprint of the collection
    >> considerably by dropping NSArray altogether. But that's a lot
    >> of coding and I'd rather not make any radical changes in my
    >> application at the moment.
    >
    > Are you sure that it is actually making a copy of the objects
    > and that, given the number of iterations, you are simply
    > exhausting memory by filling up the autorelease pool with
    > temporary objects that are created as a part of your matching algorithm?

    The search routine is very careful not to create any objects.
    (It gets called a *lot*.)

    > I would be surprised if the implementation of NSArray has any notion of a previously allocated NSEnumerator still being active on the array.

    Something does. I can easily reproduce this problem with a
    simple program. In the case where I let each enumerator run to
    the end o the collection, no extra memory is allocated. When I
    interrupt each enumerator before it gets to the end and start
    again with a new enumerator, the memory allocation starts to eat
    up RAM with a vengeance. I'll post the test application if
    anyone cares (it's only a few dozen lines).

    > Mutation of the underlying array is always unsafe during iteration and, thus, doing so is unlikely to add any safety during execution.

    Which is why I'm really surprised that -objectEnumerator would
    be duplicating the array. I can see why it would want to do that
    if it want to be thread safe or protect against modification,
    but in Cocoa that's documented as not being supported and known
    to be hazardous.

    > (3) sounds fairly futile.  NSArray and friends have been quite
    > highly optimized over time.  Certainly, there are likely more
    > optimizations possible, but they are unlikely to be simple to
    > implement.  Unless you have a highly specific usage pattern
    > for which a non-general algorithm will be a significant
    > performance win, it is unlikely that re-engineering that which
    > already exists in the Foundation is going to be much of a win.

    Well, I do have a fairly specific/simple usage pattern. Once the
    array is built, just about the only thing I do with it is
    iterate over it in various ways. So it could be easily replaced
    with a static array allocated with malloc() or NSData.

    --
    James Bucanek
  • On Sep 22, 2007, at 3:20 PM, James Bucanek wrote:

    > Bill Bumgarner <mailto:<bbum...> wrote (Saturday, September 22,
    > 2007 12:05 PM -0700):
    >
    >> On Sep 22, 2007, at 11:36 AM, James Bucanek wrote:
    >>> 1 - Use objectAtIndex: to iterate through the array.
    >>>
    >>> 2 - Create an auto release pool and discard the NSEnumerator
    >>> during every search.
    >>
    >> This is likely a good idea to do regardless of the rest of the
    >> problem as it is likely that you are causing, directly or
    >> indirectly, temporary objects to be created.  Dumping 'em every
    >> so often when doing lots of looping is generally a good idea.
    >
    > I was doing that already (once per collection), but not
    > aggressively enough to avoid this problem. In fact, if the
    > NSEnumerator wasn't making whole copies of the entire array every
    > time a new enumerator was created, there wouldn't be any problem at
    > all. During the search loop, I really only expected one object (the
    > enumerator) to be created, so at most there would have been N
    > objects created per auto release pool.

    Let's just try an experiment:

    #include <Foundation/Foundation.h>
    #include <stdio.h>

    int main(int argc, const char **argv)
    {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSMutableArray *a = [NSMutableArray array];
    for (int i=0;i<30000;i++) {
      [a addObject: [NSNumber numberWithInt: i]];
    }
    NSLog(@"%d objects",[a count]);
    NSEnumerator *e1 = [a objectEnumerator];
    NSNumber *v1;
    while ((v1 = [e1 nextObject]) != nil) {
      NSAutoreleasePool *p2 = [[NSAutoreleasePool alloc] init];
      BOOL show = ([v1 intValue] % 1000) == 0;
      if (show) NSLog(@"%@",v1);
      NSEnumerator *e2 = [a objectEnumerator];
      NSNumber *v2;
      while ((v2 = [e2 nextObject]) != nil) {
      if ([v1 isEqualTo: v2]) {
        if (show) NSLog(@"%@ = %@", v1, v2);
        break;
      }
      }
      [p2 release];
    }
    NSLog(@"Enumerator %@, v1 = %@",e1,v1);
    return 0;
    }

    If I compile and run this, here's what top says:

      PID COMMAND      %CPU  TIME  #TH #PRTS #MREGS RPRVT  RSHRD
    RSIZE  VSIZE
    16881 a.out      88.5%  0:04.88  1    13    31  1.23M  624K
    4.00M  36.7M

    It does take a while to fully execute, but memory usage never goes
    above this.

    If p2 is created _after_ e2 (so e2 is in the outer pool), memory
    skyrockets, everything comes to a screeching halt.

    So the behavior might be a bit surprising, but the solution is simple.

    (For the record, it doesn't seem to matter if the array is mutable or
    not - making an immutable copy of the array appears to have the same
    problem)

    Glenn Andreas                      <gandreas...>
      <http://www.gandreas.com/> wicked fun!
    quadrium | flame : flame fractals & strange attractors : build,
    mutate, evolve, animate
  • On Sep 22, 2007, at 1:20 PM, James Bucanek wrote:
    > Which is why I'm really surprised that -objectEnumerator would be
    > duplicating the array. I can see why it would want to do that if it
    > want to be thread safe or protect against modification, but in Cocoa
    > that's documented as not being supported and known to be hazardous.

    Ahh... I misunderstood.  It isn't copying the array's contents, it is
    copying the array of references.  Which, in this case, appears to be
    enough to push your app over the edge.

    Yes -- it is being overly safe above and beyond the documentation.  I
    would not be surprised to see said safety net go away in the future in
    light of better warnings or errors when doing unsafe operations.

    >> (3) sounds fairly futile.  NSArray and friends have been quite
    >> highly optimized over time.  Certainly, there are likely more
    >> optimizations possible, but they are unlikely to be simple to
    >> implement.  Unless you have a highly specific usage pattern for
    >> which a non-general algorithm will be a significant performance
    >> win, it is unlikely that re-engineering that which already exists
    >> in the Foundation is going to be much of a win.
    >
    > Well, I do have a fairly specific/simple usage pattern. Once the
    > array is built, just about the only thing I do with it is iterate
    > over it in various ways. So it could be easily replaced with a
    > static array allocated with malloc() or NSData.

    In that case, go for it...  No controllers or bindings or array-like
    operations (valueForKey: makeObjectsPerformSelector: (sp)) is a pretty
    simple use case.

    -objectAtIndex: may likely be fast enough, though?

    b.bum
  • On 23/09/2007, at 4:36 AM, James Bucanek wrote:

    > 1 - Use objectAtIndex: to iterate through the array.
    >
    > 2 - Create an auto release pool and discard the NSEnumerator during
    > every search.
    >
    > 3 - Abandon NSArray and roll my own collection.
    >
    > I'm leaning towards (1) because it minimizes the code change and
    > shouldn't incur too much additional overhead. (2) will keep me from
    > running out of memory, but doesn't guarantee that other calls to
    > objectEnumerator won't make gratuitous copies of the array.
    >
    > (3) is ultimately the best solution, as I can increase the speed
    > and reduce the memory footprint of the collection considerably by
    > dropping NSArray altogether. But that's a lot of coding and I'd
    > rather not make any radical changes in my application at the moment.

    There is a fourth option which is that you implement your own
    enumerator. Something like this would do:

    @interface MemoryEfficientEnumerator : NSEnumerator {
      int index;
      unsigned numObjects;
      NSArray *array;
    }
    @end

    @implementation MemoryEfficientEnumerator

    - (MemoryEfficientEnumerator *)initWithArray:(NSArray *)anArray
    {
      if (![super init])
        return nil;

      numObjects = [anArray count];
      array = [anArray retain];
    }

    + (MemoryEfficientEnumerator *)enumeratorWithArray:(NSArray *)anArray
    {
      return [[[MemoryEfficientEnumerator alloc]
           initWithArray:anArray] autorelease];
    }

    - (void)dealloc
    {
      [array release];
      [super dealloc];
    }

    - (id)nextObject
    {
      if (index >= numObjects)
        return nil;

      return [array objectAtIndex:index++];
    }

    @end

    @interface NSArray (MemoryEfficientEnumerator)
    - (MemoryEfficientEnumerator *)memoryEfficientEnumerator;
    @end

    @implementation NSArray (MemoryEfficientEnumerator)

    - (MemoryEfficientEnumerator *)memoryEfficientEnumerator
    {
      return [MemoryEfficientEnumerator enumeratorWithArray:self];
    }

    @end
  • glenn andreas <mailto:<gandreas...> wrote (Saturday,
    September 22, 2007 1:52 PM -0500):

    > On Sep 22, 2007, at 3:20 PM, James Bucanek wrote:
    >
    >> Bill Bumgarner <mailto:<bbum...> wrote (Saturday,
    >> September 22, 2007 12:05 PM -0700):
    >>
    >>> On Sep 22, 2007, at 11:36 AM, James Bucanek wrote:
    >>>> 1 - Use objectAtIndex: to iterate through the array.
    >>>>
    >>>> 2 - Create an auto release pool and discard the NSEnumerator
    >>>> during every search.
    >>>
    >>> This is likely a good idea to do regardless of the rest of
    >>> the problem as it is likely that you are causing, directly or
    >>> indirectly, temporary objects to be created.  Dumping 'em
    >>> every so often when doing lots of looping is generally a good idea.
    >>
    >> I was doing that already (once per collection), but not
    >> aggressively enough to avoid this problem. In fact, if the
    >> NSEnumerator wasn't making whole copies of the entire array
    >> every time a new enumerator was created, there wouldn't be any
    >> problem at all. During the search loop, I really only expected
    >> one object (the enumerator) to be created, so at most there
    >> would have been N objects created per auto release pool.
    >
    > Let's just try an experiment:
    >
    > #include <Foundation/Foundation.h>
    > #include <stdio.h>
    >
    > int main(int argc, const char **argv)
    > {
    > NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    > NSMutableArray *a = [NSMutableArray array];
    > for (int i=0;i<30000;i++) {
    > [a addObject: [NSNumber numberWithInt: i]];
    > }
    > NSLog(@"%d objects",[a count]);
    > NSEnumerator *e1 = [a objectEnumerator];
    > NSNumber *v1;
    > while ((v1 = [e1 nextObject]) != nil) {
    > NSAutoreleasePool *p2 = [[NSAutoreleasePool alloc] init];
    > BOOL show = ([v1 intValue] % 1000) == 0;
    > if (show) NSLog(@"%@",v1);
    > NSEnumerator *e2 = [a objectEnumerator];
    > NSNumber *v2;
    > while ((v2 = [e2 nextObject]) != nil) {
    > if ([v1 isEqualTo: v2]) {
    > if (show) NSLog(@"%@ = %@", v1, v2);
    > break;
    > }
    > }
    > [p2 release];
    > }
    > NSLog(@"Enumerator %@, v1 = %@",e1,v1);
    > return 0;
    > }
    >

    Thanks, Glenn. That's pretty close to my test app.

    > If I compile and run this, here's what top says:
    >
    > PID COMMAND      %CPU  TIME  #TH #PRTS #MREGS RPRVT  RSHRD  RSIZE  VSIZE
    > 16881 a.out      88.5%  0:04.88  1    13    31  1.23M  624K  4.00M  36.7M
    >
    > It does take a while to fully execute, but memory usage never goes above this.
    >
    > If p2 is created _after_ e2 (so e2 is in the outer pool), memory skyrockets, everything comes to a screeching halt.
    >
    > So the behavior might be a bit surprising, but the solution is simple.

    Not really. The enumerator might still be making duplicates of
    the entire collection, this code just recovers the memory each
    time before returning. I'm not particularly worried about the
    overhead of creating an extra autorelease pool, but the idea of
    duplicating the entire collection every time it gets searched is
    a horrific performance hit for large collections.

    It appears that this only occurs when there are unreleased
    NSEnumerator objects that haven't finished enumerating through
    the collection. If this was single-threaded code that could
    guarantee that the enumerator got released before returning,
    that would probably prevent the problem (duplicating the array)
    from occurring. But this is multi-threaded code and without
    adding synchronization locks to the code, there's no way to
    prevent two or more enumerators from existing at the same time.
    (Nor, should I have to in my NSHO.)

    > (For the record, it doesn't seem to matter if the array is mutable or not - making an immutable copy of the array appears to have the same problem)

    Which makes the problem even more bizarre, in my mind.

    --
    James Bucanek
  • Chris Suter <mailto:<chris...> wrote (Sunday,
    September 23, 2007 4:18 PM +1000):

    > On 23/09/2007, at 4:36 AM, James Bucanek wrote:
    >
    >> 1 - Use objectAtIndex: to iterate through the array.
    >>
    >> 2 - Create an auto release pool and discard the NSEnumerator
    >> during every search.
    >>
    >> 3 - Abandon NSArray and roll my own collection.
    >>
    >> I'm leaning towards (1) because it minimizes the code change
    >> and shouldn't incur too much additional overhead. (2) will
    >> keep me from running out of memory, but doesn't guarantee that
    >> other calls to objectEnumerator won't make gratuitous copies
    >> of the array.
    >>
    >> (3) is ultimately the best solution, as I can increase the
    >> speed and reduce the memory footprint of the collection
    >> considerably by dropping NSArray altogether. But that's a lot
    >> of coding and I'd rather not make any radical changes in my
    >> application at the moment.
    >
    > There is a fourth option which is that you implement your own enumerator. Something like this would do:

    <code snipped>

    That's an excellent solution, Chris. It has the advantage that
    it's a drop-in replacement for NSEnumerator in my code. It has
    the disadvantage that it's just a layer of abstraction over
    objectAtIndex:, so it would be slower than the built-in
    NSEnumerator. But it's worth profiling to see just home much slower.

    James
    --
    James Bucanek
  • On 23/09/2007, James Bucanek <subscriber...> wrote:
    > I was eventually able to isolate the problem; My application is
    > running out of memory. The crash occurs at
    >
    > NSMutableArray* files;
    > ...
    > NSEnumerator* e = [files objectEnumerator];  <-- crashes in
    > CFArrayGetValues > __memcpy
    >

    I think the problem is that you are using an NSMutableArray:
    The contents of a mutable array can be modified at any time, this is
    problematic for an enumerator as if the array is modified while it is
    enumerating through it, it could end up skipping elements or some
    other funky behaviour (even thought the documentation discourages this
    type of modification).

    The behaviour you're seeing is a way to combat that---make a copy of
    all the references in the array at the time of creation of the
    enumerator and enumerate though that list (which will never change).

    If you make a single copy of the array with [NSArray
    arrayWithArray:files] (which is what Chris' MemoryEfficientEnumerator
    wraps), and use that for your enumerator, you should get more
    memory-efficient, an safe behaviour (DISCLAIMER: I haven't actually
    tested this, it just seems sensible).

    -Phil
  • On 23 Sep 2007, at 19:39, James Bucanek wrote:

    > Chris Suter <mailto:<chris...> wrote (Sunday,
    > September 23, 2007 4:18 PM +1000):
    >
    >> There is a fourth option which is that you implement your own
    >> enumerator. Something like this would do:
    >
    > <code snipped>
    >
    > That's an excellent solution, Chris. It has the advantage that it's
    > a drop-in replacement for NSEnumerator in my code. It has the
    > disadvantage that it's just a layer of abstraction over
    > objectAtIndex:, so it would be slower than the built-in
    > NSEnumerator. But it's worth profiling to see just home much slower.

    If the code Chris gave is too slow, you could:

    1. Change it to get an IMP for -objectAtIndex:, then re-test.

    2. Change it to use CFArrayGetValueAtIndex(), then re-test.

    One or other of these should make it (a little bit) faster.

    (Oh, also, the -init method Chris wrote is slightly wrong... it
    *should* read:

      - (MemoryEfficientEnumerator *)initWithArray:(NSArray *)anArray
      {
        if ((self = [super init])) {
          numObjects = [anArray count];
          array = [anArray retain];
        }

        return self;
      }

    but you probably spotted that already :-)  A hazard of typing code in
    Mail.app, I'm sure [perhaps there should be a mail plug-in for
    compiling simple test programs to make sure they work :-D])

    Kind regards,

    Alastair.

    --
    http://alastairs-place.net
  • Philip Q <mailto:<p.lists...> wrote (Monday,
    September 24, 2007 12:10 AM +1200):

    > On 23/09/2007, James Bucanek <subscriber...>  wrote:
    >> I was eventually able to isolate the problem; My application is
    >> running out of memory. The crash occurs at
    >>
    >> NSMutableArray* files;
    >> ...
    >> NSEnumerator* e = [files objectEnumerator];  <-- crashes in
    >> CFArrayGetValues > __memcpy
    >>
    >
    > I think the problem is that you are using an NSMutableArray:

    Glenn Andreas confirmed that this occurs with immutable arrays too.

    > The contents of a mutable array can be modified at any time, this is
    > problematic for an enumerator as if the array is modified while it is
    > enumerating through it, it could end up skipping elements or some
    > other funky behaviour (even thought the documentation discourages this
    > type of modification).
    >
    > The behaviour you're seeing is a way to combat that---make a copy of
    > all the references in the array at the time of creation of the
    > enumerator and enumerate though that list (which will never change).

    There are two problems with this theory:

    (a) It this were true (and I totally understand what you're
    saying -- I've written collection classes that do something
    similar), why doesn't it happen all the time? According to this
    theory, any enumerator would have to first take a snapshot of
    the collection before it could begin. Of course, I'll temper
    this by saying that there could be some lazy optimization (where
    the first enumerator just sets a flag and the second one forces
    a copy, etc.). But reason (b) makes this all a moot argument.

    (b) The Cocoa collection classes are clearly documented as *not*
    supporting modifications to the collections during an
    enumeration. All clearly state that changing the underlaying
    collection is not supported and will result in undefined
    behavior. If you need to modify a collection during an
    enumeration, it's the programmer's responsibility to make a copy
    of it first.

    What I suspect is that there's some code in the Cocoa framework
    that trying to provide safe enumerators: enumerators that are
    stable while the collection is concurrently modified. This might
    be new code added to support this, or code that was just left in
    the framework after it was decided that the collection classes
    can't/don't support this pattern.

    --
    James Bucanek
  • Alastair Houghton <mailto:<alastair...> wrote
    (Monday, September 24, 2007 7:58 AM +0100):
    > If the code Chris gave is too slow, you could:
    >
    > 1. Change it to get an IMP for -objectAtIndex:, then re-test.
    >
    > 2. Change it to use CFArrayGetValueAtIndex(), then re-test.

    Those are excellent suggestions.

    For the record, I replaced my NSEnumerator with a loop that
    calls -[NSArray count] once to get the size, then calls
    -[NSArray objectAtIndex:] for each element. The performance is
    essentially identical to the loop that uses NSEnumerator (within 1%).

    --
    James Bucanek
  • Hi,

    [snip]

    > 3 - Abandon NSArray and roll my own collection.

    If you investigate this option further, you may want to evaluate use of an STL container with Objective C++. Besides being well tested and having very well defined behavior, inlining can give you dramatic performance boosts in some cases - it depends on your data and algorithm, of course.
    Unfortunately there's a learning curve connected with STL, its programming style differs considerable from Cocoa/Core Foundation.

    Best regards
    Kai
previous month september 2007 next month
MTWTFSS
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Go to today