同步读写访问实例variables在iOS中的高性能?
什么是最好的方式/最less等待的方式来同步读取/写入访问的Objective-C中的实例variables为iOS?
variables读取和写入的频率很高(假设每秒读写1000次)。 更改立即生效并不重要。 读取获得一致的数据并不重要,但写入迟早会反映在读取数据中。 有一些数据结构允许这个吗?
我想到了这个:
- 创build两个variables而不是一个variables; 我们称它们为
v[0]
和v[1]
。 - 对于每个
v[i]
,创build一个并发调度队列来构build一个读写器locking机制。 我们称他们为q[i]
。 - 现在对于写操作,只有
v[0]
被写入,遵循q[0]
的locking机制。 - 在读取操作中,首先读取
v[1]
,并且仅在某个机会(例如1%)下读取操作查看v[0]
并在必要时更新v[1]
。
下面的伪代码说明了这一点:
typedef int VType; // the type of the variable VType* v; // array of first and second variable dispatch_queue_t* q; // queues for synchronizing access to v[i] - (void) setV:(VType)newV { [self setV:newV at:0]; } - (void) setV:(VType)newV at:(int)i { dispatch_barrier_async(q[i], ^{ v[i] = newV; }); } - (VType) getV:(int)i { __block VType result; dispatch_sync(q[i], ^{ result = v[i]; }); return result; } - (VType) getV { VType result = [self getV:1]; if ([self random] < 0.01) { VType v0_result = [self getV:0]; if (v0_result != result) { [self setV:v0_result at:1]; result = v0_result; } } return result; } - (float) random { // some random number generator - fast, but not necessarily good }
这有以下好处:
-
v[0]
通常不会被读操作占用。 因此,写入操作通常不会被阻止。 -
在大多数情况下,
v[1]
不会被写入,因此读取操作通常不会被阻塞。 -
但是,如果发生许多读操作,最终写入的值将从
v[0]
传播到v[1]
。 有些值可能会丢失,但这对我的应用程序无关紧要。
你们觉得呢,这个工作吗? 有更好的解决scheme吗?
更新:
一些性能基准testing(一次读取和写入一个基准testing可以同时尽快完成1秒,一个读取队列,一个写入队列):
在iOS 7的iPhone 4S上:
runMissingSyncBenchmark: 484759 w/s runMissingSyncBenchmark: 489558 r/s runConcurrentQueueRWSyncBenchmark: 2303 w/s runConcurrentQueueRWSyncBenchmark: 2303 r/s runAtomicPropertyBenchmark: 460479 w/s runAtomicPropertyBenchmark: 462145 r/s
在模拟器与iOS 7:
runMissingSyncBenchmark: 16303208 w/s runMissingSyncBenchmark: 12239070 r/s runConcurrentQueueRWSyncBenchmark: 2616 w/s runConcurrentQueueRWSyncBenchmark: 2615 r/s runAtomicPropertyBenchmark: 4212703 w/s runAtomicPropertyBenchmark: 4300656 r/s
到目前为止,primefaces属性赢了。 极大。 这是用一个SInt64
testing的。
我期望与并发队列的方法在性能上与primefaces属性类似,因为它是r / w同步机制的标准方法。
当然, runMissingSyncBenchmark
有时会产生显示写入SInt64
已经完成。
也许, spinlock
将是最优的(见man 3 spinlock)。
由于旋转locking可以被testing,如果它是当前被locking的(这是一个快速的操作),如果旋转locking由作者任务保持,读取器任务可以只返回以前的值。
也就是说,读者任务使用OSSpinLockTry()
,只有获得锁才能获取实际值。 否则,读取任务将使用以前的值。
OSSpinLockLock()
任务将分别使用OSSpinLockLock()
和OSSpinLockUnlock()
来自动更新值。
从手册页:
名称OSSpinLockTry,OSSpinLockLock,OSSpinLockUnlock – primefaces旋转locking同步原语
概要
#include <libkern/OSAtomic.h> bool OSSpinLockTry(OSSpinLock *lock); void OSSpinLockLock(OSSpinLock *lock); void OSSpinLockUnlock(OSSpinLock *lock);
描述
自旋锁是一种简单,快速,线程安全的同步原语,适用于争用预期较低的情况。 自旋锁操作使用内存屏障来同步对由锁保护的共享内存的访问。 抢锁时可以抢占。
OSSpinLock
是一个整数types。 约定是解锁为零,locking非零。 锁必须自然alignment,不能在caching禁止的内存中。
OSSpinLockLock()
将在已locking的情况下旋转,但采用各种策略退避,使其免受大多数优先级反转活锁的影响。 但是因为它可以旋转,所以在某些情况下可能效率低下。
OSSpinLockTry()
在locking时立即返回false,如果locking为true,则返回true。 它不旋转。
OSSpinLockUnlock()
通过置零来无条件解锁。返回值
OSSpinLockTry()
在locking时返回true,如果locking已经被locking,则返回false。
我认为CouchDeveloper在同步锁中使用try
-checks的build议是一个有趣的可能性。 在我的特定实验中,它对自旋锁的影响可以忽略不计,pthread读写锁的适度增益,以及简单互斥锁的最显着影响)。 我敢打赌,不同的configuration也会在自旋锁上获得一些收益,但是我必须得到自旋锁的争用,才能使用可观察的影响。
如果您正在处理不可变或基本的数据types,则还可以使用“ 线程编程指南”中的“ 同步工具”部分中所述的atomic
属性:
primefaces操作是一种简单的同步forms,适用于简单的数据types。 primefaces操作的优点是它们不会阻塞竞争的线程。 对于简单的操作,比如增加一个计数器variables,这可能会导致比locking更好的性能。
不知道你已经做了你自己的基准testing,我testing了一些在这个文档中讨论过的技术(使用或不使用“try”algorithm进行互斥锁和pthread读/写锁)以及GCD读写器模式。 在我的testing中,我做了500万的读取,同时做了500k随机值的写入。 这产生了以下基准(以秒为单位测量,较小则更好)。
| 表格| 模拟器| 设备| + --------------------------- + ----------- --------- + - + | primefaces| 1.9 | 7.2 | | Spinlock w / o尝试| 2.8 | 8.0 | | Pthread RWlockingw / try | 2.9 | 9.1 | | 互斥锁w / try | 2.9 | 9.4 | | GCD读写器模式| 3.2 | 9.1 | | Pthread RW锁没有尝试| 7.2 | 22.2 | | NSLock | 23.1 | 89.7 | | 互斥锁无w / o尝试| 24.2 | 80.2 | | @synchronized | 25.2 | 92.0 |
底线,在这个特定的testing中,primefaces性能performance最好。 显然,primefaces属性有很大的局限性,但在你的场景中,这听起来像是可以接受的。 这些结果显然会受到你场景的细节的影响,听起来好像你的testing已经证实primefaces属性为你带来了最好的性能。