我有一个小值类,我创建了很多实例。通常具有相同的价值。此类用作一种标识符,因此主要用途是将此类的实例相互比较(通过isEqual:
)。
为了节省一些内存和时间比较,我只保留NSHashTable
中的唯一实例,并使用指针比较代替isEqual:
。
所以我指定的初始化程序如下所示:
- initWithStuff: (NSString *)stuff;
{
self = [super init];
if (!self) return nil;
// ... (actual initialisation code omitted)
hash = UniquingHashTable();
static OSSpinLock spinlock = OS_SPINLOCK_INIT;
OSSpinLockLock( &spinlock );
id member = [hash member: self];
if (member) self = member;
else [hash addObject: self];
OSSpinLockUnlock( &spinlock );
return self;
}
UniquingHashTable()
返回全局NSHashTable
或通过[NSHashTable weakObjectsHashTable]
创建它(如果它尚不存在)。 weakObjectsHashTable
是重要的一点 - 它存储了对象的弱指针,一旦没有对该对象的其他强引用,它就会被自动删除。由于dispatch_once
,初始化是线程安全的。
这很好用,峰值内存使用率显着降低,我的所有测试用例仍然通过。但我不太确定我是否可以依靠指针比较来测试相等性。 是否存在任何情况或竞争条件,我同时获得两个不同的实例(因此指针不同)但它们仍然比较等于isEqual:
?
鉴于
MyClass *a = [[MyClass alloc] initWithStuff: stringA];
MyClass *b = [[MyClass alloc] initWithStuff: stringB];
我们总是有
[a isEqual: b] == [stringA isEqual: stringB];
新代码没有改变。
我想要实现的目标也是
[a isEqual: b] == (a == b)
这样我就可以用更快的指针比较替换isEqual:
。 (是的,我测量了这一点,这将是重要的)。
对于单线程代码或者如果我使用的是NSMutableSet
而不是弱NSHashTable
,这总是有效的。我只是不确定是否有任何竞争条件,所以我可以有
[a isEqual: b] && (a != b)
是真的。
答案 0 :(得分:4)
该代码应该没问题,但我会建议一些可能的更改。这些是基于假设/观察,可能适用也可能不适用。
如果可能,将唯一代码移动到类[factory]方法,并将stuff
的值唯一。这可能是伪代码,但似乎stuff
是初始化状态的唯一来源。无论如何,这样做会在碰撞[缓存]案例中保存alloc
/ dealloc
的传递。
假设没有其他内容使用UniquingHashTable()
,请将static
初始化程序的dispatch_once()
声明移到initWithStuff:
(或工厂)方法中。它使代码更清晰,更不易碎(因为没有任何东西可以在外部使用哈希表)。
使用串行GCD队列在缓存查找代码中进行序列化;队列非常便宜,而且通常不必越过用户 - >内核屏障来排队和执行一个块。
哈希表将使用hash
和isEqual:
的组合来准确地确定对象相等性(实现细节,实际上......因为弱哈希表使用了对象个性,isEqual:
将被用作决定平等的最终决定权)。因此,哈希冲突应该不是问题。如果要确保不同地址的两个对象不相等,请相应地实现isEqual:
(两个相等的对象必须具有相同的对象) hash,两个不等的对象可能具有相同的哈希值。)
isEqual:
的实现通常首先测试指针相等性,并且只有在失败的情况下,它才会在测试内部状态的必要兔洞中进行测试,以确保相等。
如果您有自定义isEqual:
,则应该这样做。
您的代码(最初实施的)已经在碰撞案例中产生了malloc
和free
(通过+alloc
和-dealloc
)的开销。与isEqual:
的一些调用相比,这将是一个看似巨大的开销(假设您的哈希表不是非常大的冲突)。
您如何衡量isEqual:
与指针比较的开销?