Производительность NSEnumerator против цикла в Cocoa

Я знаю, что если у вас есть цикл, который изменяет количество элементов в цикле, использование NSEnumerator в наборе - лучший способ убедиться, что ваш код взорван, однако я хотел бы понять компромисс производительности между класс NSEnumerator и только старая школа для цикла

Ответы

Ответ 1

Использование нового синтаксиса for (... in ...) в Objective-C 2.0, как правило, является самым быстрым способом итерации по коллекции, поскольку он может поддерживать буфер в стеке и получать в него партии элементов.

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

Выполнение вашей собственной итерации - например, с помощью -[NSArray objectAtIndex:] - обычно будет падать где-то посередине, потому что, пока у вас не будет потенциальных накладных расходов на копирование, вы также не будете получать партии объектов из базовой коллекции.

(PS - этот вопрос должен быть помечен как Objective-C, а не C, поскольку NSEnumerator является классом Cocoa, а новый синтаксис for (... in ...) специфичен для Objective-C.)

Ответ 2

После запуска теста несколько раз результат почти такой же. Каждый блок измерения выполняется 10 раз подряд.

Результат в моем случае от самого быстрого до самого медленного:

  • For..in (testPerformanceExample3) (0.006 сек)
  • Пока (testPerformanceExample4) (0,026 сек)
  • Для (;;) (testPerformanceExample1) (0,027 сек)
  • Блок перечисления (testPerformanceExample2) (0,067 сек)

Цикл for и while почти то же самое.

comparation between iterations

tmp - это NSArray, который содержит 1 миллион объектов от 0 до 999999.

- (NSArray *)createArray
{
    self.tmpArray = [NSMutableArray array];
    for (int i = 0; i < 1000000; i++)
    {
        [self.tmpArray addObject:@(i)];
    }
    return self.tmpArray;
}

Весь код:

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (strong, nonatomic) NSMutableArray *tmpArray;
- (NSArray *)createArray;

@end

ViewController.m

#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self createArray];
}

- (NSArray *)createArray
{
    self.tmpArray = [NSMutableArray array];
    for (int i = 0; i < 1000000; i++)
    {
        [self.tmpArray addObject:@(i)];
    }
    return self.tmpArray;
}

@end

MyTestfile.m

#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>

#import "ViewController.h"

@interface TestCaseXcodeTests : XCTestCase
{
    ViewController *vc;
    NSArray *tmp;
}

@end

@implementation TestCaseXcodeTests

- (void)setUp {
    [super setUp];
    vc = [[ViewController alloc] init];
    tmp = vc.createArray;
}

- (void)testPerformanceExample1
{
    [self measureBlock:^{
        for (int i = 0; i < [tmp count]; i++)
        {
            [tmp objectAtIndex:i];
        }
    }];
}

- (void)testPerformanceExample2
{
    [self measureBlock:^{
        [tmp enumerateObjectsUsingBlock:^(NSNumber *obj, NSUInteger idx, BOOL *stop) {
           obj;
        }];
    }];
}

- (void)testPerformanceExample3
{
    [self measureBlock:^{
        for (NSNumber *num in tmp)
        {
            num;
        }
    }];
}

- (void)testPerformanceExample4
{
    [self measureBlock:^{
        int i = 0;
        while (i < [tmp count])
        {
            [tmp objectAtIndex:i];
            i++;
        }
    }];
}

@end

Для получения дополнительной информации посетите: Яблоки "О тестировании с помощью Xcode"

Ответ 3

Они очень похожи. С Objective-C 2.0 в большинстве перечислений теперь по умолчанию используется NSFastEnumeration, который создает буфер адресов для каждого объекта в коллекции, который он может доставить. Один шаг, который вы сохраняете по сравнению с классическим для цикла, не должен вызывать objectAtIndex:i каждый раз внутри цикла. Внутренности коллекции, которую вы перечисляете, реализуют быстрое перечисление без вызова objectAtIndex:i method.

Буфер является частью причины, по которой вы не можете мутировать коллекцию при перечислении, адрес объектов изменится, и созданный буфер больше не будет соответствовать.

В качестве бонуса формат в 2.0 выглядит так же хорошо, как классический для цикла:

for ( Type newVariable in expression ) { 
    stmts 
}

Прочтите следующую документацию, чтобы углубиться: Ссылка протокола NSFastEnumeration