NSMutableDictionary 比 Java Map 慢得多……为啥?

Posted

技术标签:

【中文标题】NSMutableDictionary 比 Java Map 慢得多……为啥?【英文标题】:NSMutableDictionary much slower than Java Map... why?NSMutableDictionary 比 Java Map 慢得多……为什么? 【发布时间】:2015-07-15 20:40:16 【问题描述】:

以下代码将简单的值持有者映射到对象,在 Java 中的运行速度比使用 XCode 7 beta3 的 Objective-C 快 15 倍以上,“Fastest, Aggressive Optimizations [-Ofast]”。我可以在 Java 中获得超过 280M 的查找/秒,但在 objc 示例中只有大约 19M。 (我在这里发布了相应的 Java 代码,因为这是作为 Swift 比较开始的:Swift Dictionary slow even with optimizations: doing uncessary retain/release?)。

这是我真实代码的简化版本,它肯定受哈希查找时间的限制,并且也表现出这种整体性能差异。在下面的测试中,我正在测试 null 的值,只是为了确保编译器不会优化查找,但在实际应用中,我会在大多数情况下使用该值。

当我查看工具时,我发现很多时间都花在了保留/释放、msgSend 以及一些我不理解的锁定调用上。

任何关于什么可以解释这比 Java 慢 10-15 倍的想法或任何解决方法都将不胜感激。我实际上可以实现一个完美的哈希,如下所示,所以如果我能找到一个,我可以为 ios 使用一个快速的 int-object 字典。

@interface MyKey : NSObject <NSCopying>
    @property int xi;
@end

@implementation MyKey
    - (NSUInteger)hash  return self.xi; 
    - (BOOL)isEqual:(id)object     return ((MyKey *)object).xi == self.xi; 
    - (id)copyWithZone:(NSZone *)zone  return self; 

@end

    NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:2501];
    NSObject *obj = [[NSObject alloc] init];

    int range = 2500;
    for (int x=0; x<range; x++) 
        MyKey *key = [[MyKey alloc] init];
        key.xi=x;
        [map setObject:obj forKey:key];
    

    MyKey *key = [[MyKey alloc] init];
    int runs = 50;
    for (int run=0; run<runs; run++)
    
        NSDate *start = [NSDate date];

        int reps = 10000;
        for(int rep=0; rep<reps; rep++)
        
            for (int x=0; x<range; x++) 
                key.xi=x;
                if ( [map objectForKey:key] == nil )  NSLog(@"missing key"); 
            
        

        NSLog(@"rate = %f", reps*range/[[NSDate date] timeIntervalSinceDate:start]);
    

【问题讨论】:

使用NSNumber 代替MyKey 使性能翻倍,这表明MyKey 负责大约1/2 的性能。同意NSNumber 可能不是最好的性能测试。但是哈希和相等方法以及为测试创建的关键对象是时间的一部分存在时间问题。 我并不是要指出 Objective-C NSMutableDictionary 甚至接近 Java 实现的速度。但我对这种差异感到惊讶。 你说得对,使用 NSNumber 更快,我不知道为什么。仅供参考,我使用可变键来避免读取循环期间的任何内存分配。 有趣的是 mutable 本质上是一个标志。 NSNumber 被特殊处理,如果整数落在某个范围内,则不创建对象,只是将数字移位并在最低有效位之一中设置一个位。可以这样做是因为地址落在边界上,否则这些位会保留为 0。 请添加你的Java代码,否则真的很难判断。但是请注意,第一次运行 Java 代码时,它会被编译为高度优化的本机代码(通过 JIT),因此它应该非常快。 【参考方案1】:

您可以像这样重新实现您的 -isEqual: 方法以避免属性访问器:

- (BOOL) isEqual:(id)other

    return _xi == ((MyKey*)other)->_xi;

如果您的 MyKey 类可能是子类,这是不可接受的,但我从 Java 代码中看到该类是 final

【讨论】:

【参考方案2】:

NSMutableDictionary 的计算复杂度是下一个(来自 CFDictionary.h 文件):

The access time for a value in the dictionary is guaranteed to be at
worst O(N) for any implementation, current and future, but will
often be O(1) (constant time). Insertion or deletion operations
will typically be constant time as well, but are O(N*N) in the
worst case in some implementations. Access of values through a key
is faster than accessing values directly (if there are any such
operations). Dictionaries will tend to use significantly more memory
than a array with the same number of values.

意味着,几乎所有时候,访问/插入/删除的复杂度都应为 O(1)。对于 Java HashMap,您应该得到几乎相同的结果。

根据this 的研究,使用dictionaryWithCapacity: 便利初始化器没有任何好处。

如果您使用整数作为键,可能可以将字典替换为数组。

在这个WWDC session 中,他们解释了objc_msgSend 性能问题以及如何处理它们。 第一个解决方案是使用 C++ 和 STL 容器。第二种是使用 Swift,因为与 Objective-C 不同,它只有在需要时才动态。

【讨论】:

这并不能解释 1)为什么 Java 在这个基准测试中比 ObjC 快 10-15 倍,或者 2)为什么 Swift 又慢了近 2 倍。 1) 在 Java 中,你有字节码和 JVM。比较 Java 与 Objective C / Swift 的性能是没有意义的。 2)测试取决于许多因素,如实现、编译器、优化级别等。根据这些benchmarks Swift 性能一切正常。 当然比较这些东西是有意义的。 Java,尽管它的 JIT,被认为是最慢的主流语言之一。 Objective C 和 Swift 都是静态编译的,并且有强大的编译时优化可用。预计他们会在这样一个严格的微基准测试中摧毁 Java,但实际上并没有达到一个数量级。为什么不,以及为什么优化器让我们如此失望,是值得讨论的有趣问题。您的回答甚至没有触及表面。 在我看来,使用 ARC 有一个巨大的固有惩罚,Swift 设计者通过建议我们依赖结构和值语义而有点挥手告别。但这使得这些语言难以在这些高性能应用程序中使用。 Java 在这里的“不公平”优势是,当使用固定的对象池时,引用它们是“免费的”。但是 ARC(至少目前)没有办法知道它们是长寿的,并且必须做所有这些额外的不必要的工作来引用它们。

以上是关于NSMutableDictionary 比 Java Map 慢得多……为啥?的主要内容,如果未能解决你的问题,请参考以下文章

两个NSMutableDictionary合并成一个NSMutableDictionary

如何将 NSArray 添加为 NSMutableDictionary 的 NSMutableDictionary 键?

如何获得 NSDictionary/NSMutableDictionary 的原始顺序?

类别 NSMutableDictionary/NSDictionary 应该返回 NSMutableDictionary/NSDictionary 取决于调用者类

如何更新 NSMutableDictionary?

NSMutableDictionary 奇怪的问题