Подтвердить что ты не робот

Как NSArray может быть таким медленным?

Я из мира С++/STL, и я хотел проверить, как контейнеры objective-c сравниваются с stl.

Я хотел сравнить массив чисел, но единственный способ добавить число к NSArray - это использовать NSNumber, который является крайне медленным и выпил мой баран пустой, поэтому, я думаю, мне нужно удалить их вручную. Но я не хочу тестировать побочные эффекты, поэтому я просто добавил [NSNull null] в массив.

Результаты добавления 10k вещей в массив 1k раз:
NSArray - 0,923411 секунд
vector<int> - 0.129984 секунды

Я думал, что это могут быть распределения и освобождения, поэтому я устанавливаю количество массивов (imax в коде) на 1 и количество добавлений до 10000000 (jmax), но это было еще медленнее NSArray - 2.19859 секунд
vector<int> - 0,223471 секунд

Edit:
Как уже упоминалось в комментариях, постоянный увеличивающий размер массива может быть проблемой, поэтому я сделал NSArray с помощью arrayWithCapacity, но 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;
}
4b9b3361

Ответ 1

@JeremyP обеспечивает отличную ссылку и информацию. Всегда читайте рыбу. Здесь есть некоторая разбивка того, что едет время, и что вы можете с этим поделать.

Во-первых, существует много вызовов objc_msgSend() для динамической отправки. Их можно избежать, и вы сэкономите некоторое время (хотя и не так сильно, как вы думаете. objc_msgSend() безумный оптимизирован), Но вы можете сбить его на 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. Вы можете избежать этого (и придерживаться реальных чисел, а не NSNumber), используя не сохраняющий CFMutableArray). Это приведет к тому, что время добавления составит примерно 2x из vector.

  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 уверен медленно. Как Apple могла быть такой глупой, верно? Я имею в виду, действительно... подождите... Интересно, если что-то NSMutableArray лучше, чем vector?

Попробуйте заменить эти строки своими очевидными аналогами:

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

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

(Да, включая создание и освобождение NSNumber, а не только использование NSNull.)

О, и вы тоже можете попробовать это, чтобы увидеть, насколько быстр NSMutableArray и CFMutableArray действительно может быть:

  CFArrayInsertValueAtIndex(v, 0, &j);

В моих тестах я получаю:

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

Ответ 2

Короткий ответ: Да, NSArray действительно довольно медленнее, чем классы коллекции С++ STL. Это имеет большое значение для времени компиляции и поведения во время выполнения, возможностей оптимизации со стороны компилятора и многочисленных деталей реализации.

(И, как указывает Rob, NSMutableArray оптимизирован для случайной вставки и работает лучше, чем С++ для этого...)

Реальный ответ:

Микро-бенчмарки бесполезны для оптимизации приложений, ориентированных на пользователей.

Использование микро-бенчмарка для принятия решений по внедрению - это само определение преждевременной оптимизации.

Вам будет сложно найти приложение Objective-C, ориентированное на iOS или Mac OS X, где профилирование ЦП будет показывать значительное время, потраченное на пути кода, связанные с NSArray, однако подавляющее большинство этих приложений использует коллекцию NS * классы в значительной степени исключительно.

Конечно, бывают случаи, когда производительность NS * не жизнеспособна и для этого вы переходите к С++/STL.

Ничто из этого не означает, что ваш вопрос недействителен. Без особого контекста трудно сказать, действительно ли наблюдается наблюдаемая разница в производительности (однако, по моему опыту, почти каждый раз, когда разработчик задавал вопрос на основе микро-теста, он был ошибочным).

Oh - и читайте это, поскольку он дает представление о реализации * Array.

Ответ 3

Это полноценный объект Objective-C, который означает, что каждый раз, когда вы добавляете объект из-за Cocoa алгоритма поиска сообщений, который необходим для правильной динамической привязки, есть накладные расходы каждый раз.

Также существует точка, в которой NSArrays не обязательно внутренне структурированы как непрерывный набор указателей. Для очень больших массивов NSArray работает намного лучше (т.е. Имеет намного лучшую большую временную сложность O), чем векторы С++. Почитайте это окончательный Смешной блог о рыбах на эту тему.

Ответ 4

По крайней мере, часть времени потребляется при многократном увеличении емкости NSArray. Это должно быть быстрее инициализировать NSArray вправо (или, по крайней мере, лучшую) емкость изначально с помощью

[NSMutableArray arrayWithCapacity:10000];

Ответ 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