【问题标题】:How can NSArray be this slow?NSArray 怎么会这么慢?
【发布时间】:2011-06-07 13:59:27
【问题描述】:

我来自 C++/STL 世界,我想检查一下 Objective-c 容器与 stl 相比如何。

我想比较一个数字数组,但将数字添加到 NSArray 的唯一方法是使用 NSNumber,这非常慢并且我的 ram 是空的,所以我想我需要手动释放它们。但我不想测试副作用,所以我只是将[NSNull null] 添加到数组中。

将 10k 个东西添加到数组中 1k 次的结果:
NSArray - 0.923411 秒
vector<int> - 0.129984 秒

我认为这可能是分配和释放,所以我将数组的数量(代码中的imax)设置为 1,将添加的数量设置为 10000000(jmax),但速度更慢
NSArray - 2.19859 秒
vector<int> - 0.223471 秒

编辑:
正如 cmets 中提到的,数组的不断增加的大小可能是问题,所以我使用arrayWithCapacity 制作了NSArray,但vector 也使用了reserve,它甚至比以前慢(!)(imax = 1,jmax = 10000000)。
NSArray - 2.55942
vector<int> - 0.19139
结束编辑

为什么这么慢?

我的代码供参考:

#import <Foundation/Foundation.h>
#include <vector>
#include <iostream>
#include <time.h>

using namespace std;

int main (int argc, const char * argv[])
{
    int imax = 1000;
    int jmax = 10000;

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    cout << "Vector insertions" << endl;

    clock_t start = clock();

    for(int i = 0; i < imax; i++)
    {
        vector<int> *v = new vector<int>();
        for(int j = 0; j < jmax; j++)
        {
            v->push_back(j);
        }
        delete v;
    }

    double interval = (clock() - start) / (double)CLOCKS_PER_SEC;

    cout << interval << " seconds" << endl;

    cout << "NSArray insertions" << endl;

    start = clock();

    for(int i = 0; i < imax; i++)
    {
        NSMutableArray *v = [[NSMutableArray alloc] init];
        for(int j = 0; j < jmax; j++)
        {
            [v addObject:[NSNull null]];
        }
        [v dealloc];
    }

    interval = (clock() - start) / (double)CLOCKS_PER_SEC;

    cout << interval << " seconds" << endl;

    [pool drain];
    return 0;
}

【问题讨论】:

  • 如果你想快速存储整数,为什么不使用 C 数组?
  • 存储任何东西,即使是空值也会慢 10 倍

标签: objective-c optimization stl


【解决方案1】:

@JeremyP 提供了极好的链接和信息。总是读鱼。不过,这里有一些关于进食时间的细分,以及您可能会采取的措施。

首先,有很多调用objc_msgSend() 进行动态调度。这些是可以避免的,并且您会节省一些时间(尽管没有您想象的那么多。objc_msgSend()crazy optimized)。但是跳过它可能会降低 5%:

  IMP addObject = class_getMethodImplementation([NSMutableArray class], @selector(addObject:));
  NSNull *null = [NSNull null];

  start = clock();

  for(int i = 0; i < imax; i++)
  {
    NSMutableArray *v = [[NSMutableArray alloc] init];
    for(int j = 0; j < jmax; j++)
    {
      addObject(v, @selector(addObject:), null);
    }
    [v release];
  }

retain/release 占用了很多时间。您可以通过使用非保留的CFMutableArray 来避免这种情况(并使用实数而不是NSNumber)。这将使附加时间约为 vector 的 2 倍。

  CFArrayCallBacks cb = {0};
  for(int i = 0; i < imax; i++)
  {
    CFMutableArrayRef v = CFArrayCreateMutable(NULL, 0, &cb);
    for(int j = 0; j < jmax; j++)
    {
      CFArrayAppendValue(v, &j);
    }
    CFRelease(v);
}

这个最大的成本是调用memmove()(或Mac上的收藏版)。

伙计,NSMutableArray 确实很慢。苹果怎么会这么傻,对吧?我的意思是,真的……等等……我想知道NSMutableArray 是否比vector 做得更好?

尝试将这些行替换为明显的对应行:

 v->insert(v->begin(), j);

  NSNumber *num = [[NSNumber alloc] initWithInt:j];
  [v insertObject:num atIndex:0];
  [num release];

(是的,包括创建和释放NSNumber,而不仅仅是使用NSNull。)

哦,你也可以试试这个,看看 NSMutableArrayCFMutableArray 到底有多快:

  CFArrayInsertValueAtIndex(v, 0, &j);

在我的测试中我得到:

Vector insertions
7.83188 seconds
NSArray insertions
2.66572 seconds
Non-retaining
0.310126 seconds

【讨论】:

  • 这是一个非常棒的答案。这真的是问题的核心,这是权衡。在一个基准测试中发现缓慢不应该让你想,“哇,这太慢了!”它应该让你想知道,“嗯,这有什么不同,为什么?”
【解决方案2】:

简短的回答:是的,NSArray 确实比 C++ 的 STL 集合类慢很多。这与编译时间与运行时行为、编译器方面的优化机会以及众多实现细节有很大关系。

(而且,正如 Rob 所指出的,NSMutableArray 针对随机插入进行了优化,并且在这方面的性能比 C++ 更好......)

真正的答案:

微基准对优化面向用户的应用程序毫无用处。

使用微基准做出实施决策正是过早优化的定义。

您将很难找到一个针对 iOS 或 Mac OS X 的 Objective-C 应用程序,其中 CPU 分析会显示在与 NSArray 相关的代码路径上花费的任何大量时间,但这些应用程序中的绝大多数都使用 NS* 集合几乎是专门的课程。

当然,在某些情况下 NS* 的性能不可行,为此,您需要使用 C++/STL。

这并不意味着您的问题无效。没有更多上下文,很难说观察到的性能差异是否真的很重要(但是,根据我的经验,几乎每次开发人员根据微基准提出问题时,都会被误导)。

哦——请阅读this as it gives a bit of insight into the implementation of *Array

【讨论】:

  • 虽然我同意这些结论,但我的测试表明,如果您进行随机插入,NSMutableArray 比 vector 快得多。我不会认为 NSMutableArray 普遍很慢。它只是在附加方面没有那么快。也就是说,我曾经追溯到 NSMutableArray 的唯一现实世界性能问题是复制它,因为我知道没有写时复制。
【解决方案3】:

它是一个成熟的 Objective-C 对象,这意味着每次添加对象时都会产生开销,因为 Cocoa 的消息查找算法是实现正确动态绑定所必需的。

还有一点是 NSArray 不一定在内部构造为一组连续的指针。对于非常大的数组,NSArray 比 C++ 向量执行得更好(即具有更好的大 O 时间复杂度)。阅读有关该主题的权威Ridiculous Fish Blog

【讨论】:

    【解决方案4】:

    至少有一部分时间被用于反复增加 NSArray 的容量。最初将 NSArray 初始化为正确(或至少更好)的容量应该更快:

    [NSMutableArray arrayWithCapacity:10000];
    

    【讨论】:

    • 你错了,arrayWithCapacity 实际上比没有,看看我的编辑
    • @Dani - 同意。您可以通过预先分配 NSArray 和 std::vector 并比较性能来从比较中消除扩展成本。
    • 正如@Dani 所说,预分配可能比让它扩展。我见过同样的行为。我的怀疑是因为扩展的NSArray 没有连续布局,因此它可以重用许多已经分配的小块。但是当您预分配时,它可能会尝试将所有内存集中到一个位置,这对于缓存系统来说可能是一个太大的块。只是我最初的预感。但无论如何,预分配在 Foundation 中并不是一定会赢,可能会输。
    【解决方案5】:
    #include <stdio.h>
    #include <time.h>
    
    int main (int argc, char **argv)
    {
        int imax = 1000;
        int jmax = 10000;
    
        clock_t start = clock();
    
        for(int i = 0; i < imax; i++)
        {
            int array[jmax];
            for(int j = 0; j < jmax; j++)
                j[array] = 0;
        }
    
        double interval = (clock() - start) / (double)CLOCKS_PER_SEC;
    
        printf("%f\n", interval);
    
        return 0;
    }
    

    在我的 2GHz Core2Duo iMac(使用 LLVM 编译)中的输出:

    0.000003
    

    【讨论】:

    • int array[jmax] 由编译器优化(即使使用 -O0),您在此处检查的是 10000000 movs,而不是我测试的内容
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-12
    • 1970-01-01
    • 2015-10-18
    • 1970-01-01
    • 2011-05-31
    相关资源
    最近更新 更多