Desempenho do NSEnumerator versus loop for no Cocoa
-
09-06-2019 - |
Pergunta
Eu sei que se você tem um loop que modifica a contagem dos itens no loop, usar o NSEnumerator em um set é a melhor maneira de garantir que seu código exploda, porém eu gostaria de entender as compensações de desempenho entre a classe NSEnumerator e apenas um loop for old school
Solução
Usando o novo for (... in ...)
A sintaxe no Objective-C 2.0 é geralmente a maneira mais rápida de iterar em uma coleção porque pode manter um buffer na pilha e colocar lotes de itens nele.
Usando NSEnumerator
geralmente é a maneira mais lenta porque geralmente copia a coleção que está sendo iterada;para coleções imutáveis, isso pode ser barato (equivalente a -retain
), mas para coleções mutáveis pode causar a criação de uma cópia imutável.
Fazendo sua própria iteração — por exemplo, usando -[NSArray objectAtIndex:]
- geralmente ficará em algum ponto intermediário porque, embora você não tenha a sobrecarga potencial de cópia, também não obterá lotes de objetos da coleção subjacente.
(PS - Esta questão deve ser marcada como Objective-C, não C, já que NSEnumerator
é uma classe Cocoa e a nova for (... in ...)
a sintaxe é específica do Objective-C.)
Outras dicas
Depois de executar o teste várias vezes, o resultado é quase o mesmo.Cada bloco de medida é executado 10 vezes consecutivas.
O resultado no meu caso, do mais rápido para o mais lento:
- Para..em (testePerformanceExample3) (0,006 seg.)
- Enquanto (testePerformanceExample4) (0,026 seg.)
- Para(;;) (testePerformanceExample1) (0,027 seg)
- Bloco de enumeração (testePerformanceExample2) (0,067 seg)
O loop for e while é quase o mesmo.
O tmp
é um NSArray
que contém 1 milhão de objetos de 0 a 999999.
- (NSArray *)createArray
{
self.tmpArray = [NSMutableArray array];
for (int i = 0; i < 1000000; i++)
{
[self.tmpArray addObject:@(i)];
}
return self.tmpArray;
}
Todo o código:
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
MeuArquivoTeste.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
Para mais informações visite: Maçãs "Sobre testes com Xcode"
Eles são muito parecidos.Com o Objective-C 2.0, a maioria das enumerações agora é padronizada NSFastEnumeration
que cria um buffer de endereços para cada objeto da coleção que ele pode entregar.A única etapa que você economiza no loop for clássico é não precisar chamar objectAtIndex:i
cada vez dentro do loop.Os componentes internos da coleção que você está enumerando implementam enumeração rápida sem chamar objectAtIndex:i method
.
O buffer é parte do motivo pelo qual você não pode alterar uma coleção ao enumerar, o endereço dos objetos será alterado e o buffer que foi construído não corresponderá mais.
Como bônus, o formato 2.0 parece tão bom quanto o clássico for loop:
for ( Type newVariable in expression ) {
stmts
}
Leia a seguinte documentação para se aprofundar:Referência do protocolo NSFastEnumeration