Singleton pattern: Difference between revisions

From iPhone Development Wiki
mNo edit summary
No edit summary
Line 37: Line 37:
}
}
</source>
</source>
Unfortunately, <tt>OSAtomicCompareAndSwapPtrBarrier()</tt> on ARM is implemented using a ''global'' spin lock, so in principle this is nowhere faster than <tt>pthread_once()</tt>, and is even more error-prone. If you are absolutely crazy about performance you could use the LDREX/STREX instructions, but mind you that these won't improve performance a lot since the bottleneck should be shifted to elsewhere.
Unfortunately, <tt>OSAtomicCompareAndSwapPtrBarrier()</tt> on ARMv4 is implemented using a ''global'' spin lock, so in principle this is nowhere faster than <tt>pthread_once()</tt>, and is even more error-prone. On ARMv6 it is implemented as a LDREX/STREX pair<ref>http://www.opensource.apple.com/source/Libc/Libc-583/arm/sys/OSAtomic.s</ref>:
<source lang="asm">
ENTRY_POINT(_OSAtomicCompareAndSwapPtrBarrier)
1: ldrex r3, [r2] // load existing value and tag memory
teq r3, r0 // is it the same as oldValue?
movne r0, #0 // if not, return 0 immediately
bxne lr    
strex r3, r1, [r2] // otherwise, try to store new value
cmp r3, #0 // check if the store succeeded
bne 1b // if not, try again
mov r0, #1 // return true
bx lr
</source>
 
== References ==
<references/>

Revision as of 19:10, 5 November 2009

The traditional singleton creation

+(XXClass*)sharedInstance {
  static XXClass* XXClass_sharedInst = nil;
  @synchronized(self) {
    if (XXClass_sharedInst == nil) {
      XXInitialize(&XXClass_sharedInst);
    } 
  }
  return __sharedInst;
}

is extremely inefficient because @synchronized not only lock a mutex, but also insert an exception handler (try/catch block). If you don't care about exceptions and willing to use C functions, pthread_once() is a better alternative:

static XXClass* XXClass_sharedInst = nil;
static pthread_once_t XXClass_onceControl = PTHREAD_ONCE_INIT;
static void XXInitializeOnce(void) { XXInitialize(&XXClass_sharedInst); }
...
+(XXClass*)sharedInstance {
  pthread_once(&XXClass_onceControl, &XXInitializeOnce);
  return XXClass_sharedInst;
}

pthread_once() is implemented using a spin lock (which leads to a syscall_thread_switch() kernel call in the worst case).

If it is safe to create multiple copies of the singleton and destroy the extra ones, you may even use CAS:

+(XXClass*)sharedInstance {
  static XXClass* XXClass_sharedInst = nil;
  if (XXClass_sharedInst == nil) {
    XXClass* tmp;
    XXInitialize(&tmp);
    if (!OSAtomicCompareAndSwapPtrBarrier(nil, tmp, (void*volatile*)&XXClass_sharedInst)))
      XXDestroy(tmp);
  }
  return XXClass_sharedInst;
}

Unfortunately, OSAtomicCompareAndSwapPtrBarrier() on ARMv4 is implemented using a global spin lock, so in principle this is nowhere faster than pthread_once(), and is even more error-prone. On ARMv6 it is implemented as a LDREX/STREX pair[1]:

ENTRY_POINT(_OSAtomicCompareAndSwapPtrBarrier)
1:	ldrex	r3, [r2]	// load existing value and tag memory
	teq	r3, r0		// is it the same as oldValue?
	movne	r0, #0		// if not, return 0 immediately
	bxne	lr	    
	strex	r3, r1, [r2]	// otherwise, try to store new value
	cmp	r3, #0		// check if the store succeeded
	bne	1b		// if not, try again
	mov	r0, #1		// return true
	bx	lr

References