title: Optimising CALayer setPosition
body: <p>I would like to have around 400 CALayers in an application and reposition (and scale them) all during an animated gesture. They don't need to redraw their content during this operation, just move and re-scale. I'd like to maintain animation at 60 frames a second.</p>
  
  <p>I'm targeting at least anything that can run iOS 7. It's be nice to support the iOS 6 devices too.</p>
  
  <p>To check for feasibility, I've written a small test application based on Apple's Single View template project. The code is included below.</p>
  
  <p>Profiling with instruments, the vast majority of the run time (94%) is in the display link callback. Within this 54% is spent in <code>[CALayer setPosition]</code> and 33% in <code>CA::Transaction::commit()</code>. I've included a profile showing this.</p>
  
  <p>Much of the position setting profile trace seems to be about doing lots of things that I don't need the framework to do. So, does anyone know if there is some way to turn all this stuff off and have layers be a bit more stupid :-) Ie – basically enable some kind of fast path if I just want to animate everything myself?</p>
  
  <p>As a small note, I've included a technique mentioned <a href="https://stackoverflow.com/questions/7750862/why-do-calayers-move-slower-than-uiviews">here</a> that provides some speed up by setting a null action for changes to the position key.</p>
  
  <p>If you have other ideas for speed ups (like using UIViews instead of CALayers), I'd love to hear them.</p>
  
  <p>Thanks!</p>
  
  <p>The relevant parts, I hope, of the profile:</p>
  
  <pre><code>    Running Time    Self        Symbol Name
      147914.0ms  100.0%  1754.0      -[STViewController displayLinkCallback:]
      85265.0ms   57.6%   323.0        -[CALayer setPosition:]
      83842.0ms   56.6%   8301.0        CA::Layer::set_position(CA::Vec2&lt;double&gt; const&amp;, bool)
      27247.0ms   18.4%   811.0          CA::Layer::begin_change(CA::Transaction*, unsigned int, objc_object*&amp;)
      21103.0ms   14.2%   316.0           actionForKey(CALayer*, CA::Transaction*, NSString*)
      4819.0ms    3.2%    2296.0          CAAtomGetString
      188.0ms    0.1% 188.0           -[CALayer actionForKey:]
      171.0ms    0.1% 171.0           DYLD-STUB$$x_strtod
      88.0ms    0.0%  88.0            DYLD-STUB$$-[NSString(CAMLWriter) CAMLType]
      34.0ms    0.0%  34.0            CA::Transaction::get_value(unsigned int, _CAValueType, void*)
      33.0ms    0.0%  33.0            DYLD-STUB$$-[NSNumber(CAMLWriter) CAMLType]
      23316.0ms   15.7%   730.0          CA::Layer::end_change(CA::Transaction*, unsigned int, objc_object*)
      12759.0ms    8.6%   1266.0         CA::Layer::writable_state(CA::Transaction*)
      7523.0ms    5.0%    557.0          -[NSObject(NSKeyValueObserverNotification) willChangeValueForKey:]
      1145.0ms    0.7%    1145.0         OSSpinLockLock
      594.0ms    0.4% 594.0          _Unwind_SjLj_Unregister
      327.0ms    0.2% 327.0          OSSpinLockLock$shim
      310.0ms    0.2% 310.0          CAAtomGetString
      306.0ms    0.2% 306.0          __inline_isnand
      298.0ms    0.2% 246.0          CA::Transaction::unlock()
      215.0ms    0.1% 215.0          objc_msgSend
      203.0ms    0.1% 203.0          _Unwind_SjLj_Register
      168.0ms    0.1% 168.0          -[CATextLayer didChangeValueForKey:]
      149.0ms    0.1% 149.0          DYLD-STUB$$-[NSNumber(CAMLWriter) CAMLType]
      141.0ms    0.0% 141.0          CA::Layer::property_did_change(CA::Transaction*, unsigned int)
      129.0ms    0.0% 129.0          CFRetain
      123.0ms    0.0% 123.0          CA::Transaction::lock()
      112.0ms    0.0% 112.0          CA::Transaction::ensure_compat()
      100.0ms    0.0% 100.0          _NSKeyValueRetainedObservationInfoForObject
      89.0ms    0.0%  89.0           objc_msgSend$shim
      78.0ms    0.0%  78.0           DYLD-STUB$$-[NSURL(CAMLWriter) encodeWithCAMLWriter:]
      57.0ms    0.0%  57.0           DYLD-STUB$$CAMLWriterFreeAttributeList(_CAMLWriterAttribute*)
      42.0ms    0.0%  42.0           actionForKey(CALayer*, CA::Transaction*, NSString*)
      30.0ms    0.0%  30.0           DYLD-STUB$$-[CAShapeLayer setFillColor:]
      25.0ms    0.0%  25.0           DYLD-STUB$$x_strtod
      24.0ms    0.0%  24.0           DYLD-STUB$$CFNumberFormatterSetProperty$shim
      23.0ms    0.0%  23.0           _CFRetain
      8.0ms    0.0%   8.0        magazine_alloc_
      410.0ms    0.2% 410.0         DYLD-STUB$$x_strtod
      135.0ms    0.0% 135.0         CA::Layer::begin_change(CA::Transaction*, unsigned int, objc_object*&amp;)
      133.0ms    0.0% 133.0         -[NSObject(NSKeyValueObserverNotification) willChangeValueForKey:]
      107.0ms    0.0% 107.0         CA::Transaction::unlock()
      74.0ms    0.0%  74.0          DYLD-STUB$$-[NSURL(CAMLWriter) encodeWithCAMLWriter:]
      60.0ms    0.0%  60.0          DYLD-STUB$$CAMLWriterFreeAttributeList(_CAMLWriterAttribute*)
      53.0ms    0.0%  53.0          CA::Layer::writable_state(CA::Transaction*)
      53.0ms    0.0%  53.0          DYLD-STUB$$-[NSString(CAMLWriter) CAMLType]
      27.0ms    0.0%  27.0          CA::Layer::end_change(CA::Transaction*, unsigned int, objc_object*)
      27.0ms    0.0%  27.0          CA::Transaction::ensure_compat()
      21.0ms    0.0%  21.0          DYLD-STUB$$CA::Display::DisplayLinkItem::set_user_info(void const*)
      52572.0ms   35.5%   46.0         CA::Transaction::commit()
      38450.0ms   25.9%   185.0         CA::Context::commit_transaction(CA::Transaction*)
      33772.0ms   22.8%   75.0           CA::Layer::commit_if_needed(CA::Transaction*, void (*)(CA::Layer*, unsigned int, unsigned int, void*), void*)
      33649.0ms   22.7%   36.0            CA::Layer::commit_if_needed(CA::Transaction*, void (*)(CA::Layer*, unsigned int, unsigned int, void*), void*)
      33522.0ms   22.6%   458.0            CA::Layer::commit_if_needed(CA::Transaction*, void (*)(CA::Layer*, unsigned int, unsigned int, void*), void*)
      32936.0ms   22.2%   3694.0            CA::Layer::commit_if_needed(CA::Transaction*, void (*)(CA::Layer*, unsigned int, unsigned int, void*), void*)
      28899.0ms   19.5%   298.0              CA::Context::commit_layer(CA::Layer*, unsigned int, unsigned int, void*)
      84.0ms    0.0%  84.0               CA::Render::Object::unref() const
      83.0ms    0.0%  83.0               CA::Render::encode_set_object(CA::Render::Encoder*, unsigned long, unsigned int, CA::Render::Object*, unsigned int)
      54.0ms    0.0%  54.0               DYLD-STUB$$x_strtod
      47.0ms    0.0%  47.0               CA::Render::Encoder::encode_int32(unsigned int)
      36.0ms    0.0%  36.0               CA::Render::Layer::~Layer()
      22.0ms    0.0%  21.0               CA::Layer::thread_flags_(CA::Transaction*)
      17.0ms    0.0%  17.0               CA::Layer::copy_render_layer(CA::Transaction*, unsigned int, unsigned int*)
      128.0ms    0.0% 48.0              CA::Context::commit_layer(CA::Layer*, unsigned int, unsigned int, void*)
      91.0ms    0.0%  0.0          CA::Context::commit_layer(CA::Layer*, unsigned int, unsigned int, void*)
      46.0ms    0.0%  0.0         CA::Context::commit_layer(CA::Layer*, unsigned int, unsigned int, void*)
      1.0ms    0.0%   1.0         CA::Render::Object::unref() const
      1.0ms    0.0%   1.0         CA::Render::Encoder::encode_int32(unsigned int)
      1765.0ms    1.1%    61.0           CA::Render::Encoder::send_message(unsigned int, unsigned int)
      904.0ms    0.6% 15.0           _dispatch_queue_wakeup_global_slow
      711.0ms    0.4% 3.0        CA::Render::Encoder::~Encoder()
      100.0ms    0.0% 1.0        CA::Layer::layout_and_display_if_needed(CA::Transaction*)
      97.0ms    0.0%  13.0           pthread_mutex_unlock
      87.0ms    0.0%  14.0           _dispatch_async_f_slow
      85.0ms    0.0%  41.0           _pthread_mutex_lock
      74.0ms    0.0%  2.0        CA::Transaction::foreach_root(void (*)(CA::Layer*, void*), void*)
      70.0ms    0.0%  29.0           CA::Layer::prepare_commit(CA::Transaction*)
      66.0ms    0.0%  18.0           CA::Transaction::get_value(unsigned int, _CAValueType, void*)
      56.0ms    0.0%  31.0           CA::Render::Encoder::ObjectCache::encode_invalidations(CA::Render::Encoder*)
      51.0ms    0.0%  0.0        CA::Layer::collect_animations(CA::Transaction*, double, double*)
      38.0ms    0.0%  38.0           dispatch_async_f
      38.0ms    0.0%  38.0           _dispatch_queue_push_slow
      34.0ms    0.0%  32.0           CA::Context::retain_all_contexts(bool, CA::Context**, unsigned long&amp;)
      33.0ms    0.0%  12.0           CA::Render::Encoder::Encoder(x_heap_struct*, unsigned int, void*, unsigned int, double)
      31.0ms    0.0%  16.0           CA::Context::unref(bool)
      29.0ms    0.0%  29.0           CA::Layer::set_next_animation_time(CA::Transaction*, double, double)
      26.0ms    0.0%  20.0           CA::Transaction::unlock()
      20.0ms    0.0%  20.0           x_heap_malloc_small_
      19.0ms    0.0%  19.0           CA::DispatchGroup::enqueue(void (*)(void*), void*)
      17.0ms    0.0%  17.0           CARecordTransaction
      17.0ms    0.0%  17.0           CABackingStoreCollectAsync
      15.0ms    0.0%  15.0           CAMarkStatistic
      15.0ms    0.0%  15.0           CALayerGetLayer
      13.0ms    0.0%  13.0           x_mem_dealloc_chain
      10.0ms    0.0%  10.0           CA::Transaction::lock()
      8.0ms    0.0%   8.0        x_heap_new_with_ptr
      7.0ms    0.0%   7.0        x_heap_free
      6.0ms    0.0%   6.0        DYLD-STUB$$-[NSString(CAMLWriter) CAMLType]
      5.0ms    0.0%   5.0        CA::Transaction::foreach_command(unsigned int, void (*)(int, unsigned long, void const*, void*), void*)
      5.0ms    0.0%   4.0        CATimeWithHostTime
      4.0ms    0.0%   4.0        DYLD-STUB$$-[OS_dispatch_data finalize]
      4.0ms    0.0%   4.0        DYLD-STUB$$-[NSNumber(CAMLWriter) CAMLType]
      3.0ms    0.0%   3.0        OSSpinLockUnlock$shim
      3.0ms    0.0%   3.0        OSSpinLockLock$shim
      3.0ms    0.0%   3.0        pthread_mutex_lock
      3.0ms    0.0%   3.0        DYLD-STUB$$dlsym
      2.0ms    0.0%   2.0        CA::CG::Queue::collect(double)
      2.0ms    0.0%   0.0        CACurrentMediaTime
      2.0ms    0.0%   2.0        CA::Render::Encoder::set_object_cache(CA::Render::Encoder::ObjectCache*)
      2.0ms    0.0%   2.0        x_heap_malloc
      2.0ms    0.0%   2.0        __mtx_droplock
      1.0ms    0.0%   1.0        OSSpinLockLock
      1.0ms    0.0%   1.0        CA::Render::Encoder::encode_int64(unsigned long long)
      1.0ms    0.0%   1.0        CA::Layer::collect_layers_(CA::Layer::CollectLayersData*)
      1.0ms    0.0%   1.0        CA::Render::Coder::Coder(x_heap_struct*)
      1.0ms    0.0%   1.0        DYLD-STUB$$CAMLWriterFreeAttributeList(_CAMLWriterAttribute*)
      1.0ms    0.0%   1.0        x_hash_table_foreach
      1.0ms    0.0%   1.0        CA::Render::Coder::~Coder()
      1.0ms    0.0%   1.0        pthread_threadid_np
      13744.0ms    9.2%   2112.0        CA::Layer::free_transaction(CA::Transaction*)
      70.0ms    0.0%  26.0          x_hash_table_free
      67.0ms    0.0%  0.0       CA::Transaction::Level::free_levels(CA::Transaction::Level*)
      34.0ms    0.0%  12.0          x_hash_table_foreach
      30.0ms    0.0%  30.0          CALayerRelease
      29.0ms    0.0%  29.0          CA::Layer::State::unref(CA::Transaction*) const
      25.0ms    0.0%  20.0          x_hash_table_remove_if
      16.0ms    0.0%  16.0          x_hash_table_size
      11.0ms    0.0%  11.0          DYLD-STUB$$__39-[CAWindowServer displayWithDisplayId:]_block_invoke
      8.0ms    0.0%   8.0       DYLD-STUB$$-[NSString(CAMLWriter) CAMLType]
      7.0ms    0.0%   7.0       x_mem_dealloc_size
      4.0ms    0.0%   4.0       OSSpinLockLock
      4.0ms    0.0%   4.0       CA::Render::Encoder::ObjectCache::encode_invalidations(CA::Render::Encoder*)
      3.0ms    0.0%   3.0       DYLD-STUB$$usleep$shim
      3.0ms    0.0%   0.0       magazine_dealloc_
      2.0ms    0.0%   2.0       DYLD-STUB$$-[NSNumber(CAMLWriter) CAMLType]
      2.0ms    0.0%   2.0       x_heap_malloc
      2.0ms    0.0%   2.0       x_heap_free
      2.0ms    0.0%   2.0       CA::Render::Encoder::~Encoder()
      2.0ms    0.0%   2.0       DYLD-STUB$$mig_put_reply_port$shim
      2.0ms    0.0%   2.0       CA::release_root_if_unused(CA::Layer*, CA::Layer*, void*)
      2.0ms    0.0%   2.0       CA::Render::Encoder::send_message(unsigned int, unsigned int)
      1.0ms    0.0%   1.0       CAMarkStatistic
      1.0ms    0.0%   1.0       CA::Layer::collect_animations(CA::Transaction*, double, double*)
      1.0ms    0.0%   1.0       pthread_mutex_unlock
      1.0ms    0.0%   1.0       CA::release_root(CA::Layer*, CA::Layer*, void*)
      1.0ms    0.0%   1.0       x_heap_malloc_small_
      1.0ms    0.0%   1.0       _pthread_mutex_lock
      1.0ms    0.0%   1.0       x_mem_dealloc_chain
      4061.0ms    2.7%    4061.0       cos
      2317.0ms    1.5%    2317.0       objc_msgSend
      1155.0ms    0.7%    1155.0       sin
      144.0ms    0.0% 0.0      +[CATransaction setAnimationDuration:]
      104.0ms    0.0% 24.0         objc_object::sidetable_release(bool)
      92.0ms    0.0%  78.0         CA::Transaction::push()
      86.0ms    0.0%  17.0         objc_object::sidetable_retain()
      76.0ms    0.0%  76.0         objc_retain
      59.0ms    0.0%  59.0         -[__NSArrayM countByEnumeratingWithState:objects:count:]
      56.0ms    0.0%  56.0         CA::Layer::set_position(CA::Vec2&lt;double&gt; const&amp;, bool)
      42.0ms    0.0%  42.0         objc_release
      41.0ms    0.0%  41.0         DYLD-STUB$$objc::DenseMapBase&lt;objc::DenseMap&lt;objc_object*, unsigned long, true, objc::DenseMapInfo&lt;objc_object*&gt; &gt;, objc_object*, unsigned long, objc::DenseMapInfo&lt;objc_object*&gt;, true&gt;::erase(objc_object* const&amp;)
      19.0ms    0.0%  19.0         CA::Transaction::pop()
      17.0ms    0.0%  17.0         +[CATransaction begin]
      17.0ms    0.0%  13.0         CATimeWithHostTime
      15.0ms    0.0%  15.0         -[CADisplayLink timestamp]
      10.0ms    0.0%  10.0         +[CATransaction commit]
      4.0ms    0.0%   4.0      os_lock_trylock
      2.0ms    0.0%   2.0      CA::Transaction::ensure_compat()
      1.0ms    0.0%   1.0      x_hash_table_remove_if
      1.0ms    0.0%   1.0      DYLD-STUB$$-[NSString(CAMLWriter) CAMLType]
      1.0ms    0.0%   1.0      DYLD-STUB$$-[NSNumber(CAMLWriter) CAMLType]
      1.0ms    0.0%   1.0      x_heap_free
      1.0ms    0.0%   1.0      x_hash_table_free
      1.0ms    0.0%   1.0      objc::DenseMapBase&lt;objc::DenseMap&lt;objc_object*, unsigned long, true, objc::DenseMapInfo&lt;objc_object*&gt; &gt;, objc_object*, unsigned long, objc::DenseMapInfo&lt;objc_object*&gt;, true&gt;::find(objc_object* const&amp;)
  </code></pre>
  
  <p>And this is the code I added on top of the template Single View Application project:</p>
  
  <pre><code>//  STViewController.m
  //  LayerSpeedTest
  
  #import "STViewController.h"
  
  @implementation CALayer (Extensions)
  
  - (void)setNullAsActionForKeys:(NSArray *)keys
  {
    NSMutableDictionary *dict = [self.actions mutableCopy];
  
    if(dict == nil)
    {
      dict = [NSMutableDictionary dictionaryWithCapacity:[keys count]];
    }
  
    for(NSString *key in keys)
    {
      [dict setObject:[NSNull null] forKey:key];
    }
  
    self.actions = dict;
  }
  
  @end
  
  @interface STViewController ()
  @property (nonatomic, retain) NSArray *layers;
  @property (nonatomic, retain) CADisplayLink *displayLink;
  -(void) displayLinkCallback:(CADisplayLink*) displayLink;
  @end
  
  @implementation STViewController
  
  - (void)viewDidLoad
  {
    [super viewDidLoad];
  
    UIView *const view = [self view];
    [view setBackgroundColor: [UIColor blackColor]];
  
    CALayer *const layer = [view layer];
    NSMutableArray *layers = [NSMutableArray array];
  
    for(NSUInteger i=0; i&lt;24*3*4; ++i)
    {
      CATextLayer *const textLayer = [CATextLayer layer];
      [textLayer setContentsScale: [[UIScreen mainScreen] scale]];
      [textLayer setForegroundColor: [[UIColor whiteColor] CGColor]];
      [textLayer setBackgroundColor: [[UIColor clearColor] CGColor]];
      [textLayer setBounds: CGRectMake(0,0,100,100)];
      [textLayer setString: @"100"];
      [textLayer setNeedsDisplay];
      [textLayer setPosition: CGPointMake(160,200)];
      [textLayer setNullAsActionForKeys: @[@"position"]];
      [layer addSublayer: textLayer];
      [layers addObject: textLayer];
    }
  
    _layers = layers;
  
    _displayLink = [CADisplayLink displayLinkWithTarget: self selector: @selector(displayLinkCallback:)];
    [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
  }
  
  -(void) displayLinkCallback:(CADisplayLink *)displayLink
  {
    const NSTimeInterval timestamp = [displayLink timestamp]*0.3;
    [CATransaction begin];
    [CATransaction setAnimationDuration:0];
    {
      NSUInteger i=0;
      for(CALayer *const layer in _layers)
      {
        const CGFloat angle = i*0.1 + timestamp;
        [layer setPosition: CGPointMake( 160 + 100 * sin(angle), 250 + 200 * cos(angle*0.728734))];
        ++i;
      }
    }
    [CATransaction commit];
  }
  
  @end
  </code></pre>