2010-10-24 3 views
1

J'ai une application qui itère sur un tableau à chaque pas et il me semble obtenir des résultats étonnamment lents quand le tableau est vide. Donc, j'ai enquêté avec des tests de suivi qui sont allés quelque chose comme ça:Performances du tableau vide Objective-C

NSMutableArray* ar = [NSMutableArray array]; 
double time = CFAbsoluteTimeGetCurrent(); 
for (int i = 0; i < 10000; i++) 
{ 
    for (NSObject* obj in ar) 
    { 
     [obj retain]; 
     [obj release]; 
    } 
} 
time = CFAbsoluteTimeGetCurrent() - time; 
printf("Empty Time: %1.12f", time/10000.0f); 

time = CFAbsoluteTimeGetCurrent(); 
for (int i = 0; i < 10000; i++) 
{ 
    if ([ar count] > 0) 
    { 
     for (NSObject* obj in ar) 
     { 
      [obj retain]; 
      [obj release]; 
     } 
    } 
} 
time = CFAbsoluteTimeGetCurrent() - time; 
printf("Checked Time: %1.12f", time/10000.0f); 

J'ai essayé ceci pour 100 | 1,000 | 10.000 itérations intervalles avec les résultats suivants:

Empty Time: 0.000000039935   //100 
Checked Time: 0.000000020266  //100 
Empty Time: 0.000000018001   //1000 
Checked Time: 0.000000011027  //1000 
Empty Time: 0.000000015503   //10000 
Checked Time: 0.000000008899  //10000 

Étrangement, cela montre que le comte ayant simplement vérifier améliore considérablement les performances sur les pistes à faible itération (probablement à cause des systèmes de mise en cache). Ceci est absolument étonnant pour moi car je m'attendais à ce que la compilation/exécution d'Objective-C fasse déjà ce contrôle chaque fois qu'une boucle foreach est exécutée! Est-ce que quelqu'un a une idée de ce que cela pourrait être le cas et s'il y a un moyen de tirer encore plus de performance de cette configuration en boucle? Merci!

Répondre

7

Les tableaux vides ne sont pas très courants dans un programme Cocoa typique, pas plus que l'itération d'un tableau vide de dizaines de milliers de fois.

Il serait exceptionnellement surprenant de voir jamais l'énumération des tableaux vides apparaître dans les Instruments en tant que consommateur important de cycles CPU. Etant donné que la Fondation et la Fondation de base sont optimisées pour les modèles de performance du monde réel, il n'est pas surprenant qu'aucune vérification du nombre de 0 ne soit effectuée.

Toutefois, si vous avez vraiment devez itérer un tableau vide une fois bazillion, le meilleur moyen est d'utiliser un bloc:

time = CFAbsoluteTimeGetCurrent(); 
[ar enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 
    [obj retain]; 
    [obj release]; 
}]; 

Je collais votre code en principal de l'outil Fondation() et a ceci sur un relativement récent MacBook Pro:

 Empty Time: 0.000000019896 
    Checked Time: 0.000000007498 
    Block Time: 0.000000000298 

Bien sûr, au lieu de tableaux vides, il suffit d'utiliser nil. C'est à dire. J'ai fait tous les tests une deuxième fois après ar = nil;.

ar = nil; 
time = CFAbsoluteTimeGetCurrent(); 
for (int i = 0; i < 10000; i++) 
{ 
    for (NSObject* obj in ar) 
    { 
     [obj retain]; 
     [obj release]; 
    } 
} 
... etc ... 


     Empty Time: 0.000000019902 
    Checked Time: 0.000000007999 
     Block Time: 0.000000000298 
    nil Empty Time: 0.000000015599 
nil Checked Time: 0.000000004703 
    nil Block Time: 0.000000000000 

Dans l'ensemble, cependant, si vos structures de données sont que complexes et vous martèlent sur eux beaucoup sur chaque image render, je vous suggère une structure de données différentes peut-être dans l'ordre.

Bien sûr, uniquement si vous avez réellement utilisé des instruments pour échantillonner le code et que vous optimisez quelque chose qui consomme un pourcentage significatif de cycles CPU globaux.

+0

HOLY POO! L'implémentation du bloc a réduit le temps d'exécution d'un facteur de 100 ?! Il doit utiliser GCD pour ce genre de performance, oui? C'est assez incroyable, je vais devoir regarder ça. Merci! Oh, oui, et je suis en train de construire un jeu, donc l'itération sur des tableaux vides se produit PARTOUT à chaque image, donc la performance pour les tableaux vides est importante. Heureusement, la vérification du compte n'était pas aussi mauvaise que prévu (quelque chose comme 20 lignes de code de la chaudière). Merci encore! – Grimless

+0

Étrange. J'ai essayé votre implémentation en bloc et cela a triplé le temps d'exécution! Voici ce que j'ai obtenu: Heure de vérification: \t 0.000000009954 Temps vide: \t \t 0,000000016987 Temps de blocage: \t \t 0,000000037014. NOTE: cela a été fait 1000 fois, il est donc possible que la création de blocs soit vraiment ce qui a tué ça. EDIT: Oui, alors je me suis débarrassé de la boucle de fori et j'ai essayé le bloc droit, et cela a réduit le temps d'exécution de 2x. Belle solution! – Grimless

+0

OOPS! J'ai oublié d'ajuster les autres boucles. Oui, donc l'implémentation du bloc a augmenté le temps d'exécution de 3x ... Ouch. – Grimless

-2

La construction for-in n'est pas libre, elle doit être résolue en une sorte d'appel de méthode d'énumération, de sorte que les temps indiqués ont du sens. J'utiliserais un tableau C simple dans ce cas. De plus, vous obtiendrez de meilleures performances si vous utilisez objc_msgsend() pour appeler des méthodes objc dans de telles grandes boucles.

+1

Il y a deux problèmes avec cette réponse que j'ai identifiés comme inutiles et/ou dangereux. Pour un: je ne peux pas utiliser C arrays en raison de la nature dynamique des tableaux; Les objets sont ajoutés et supprimés de ces tableaux de manière continue, ce qui rend la gestion du tableau C beaucoup plus pénible. Deuxièmement: l'amorce Objective-C dit explicitement de ne jamais appeler objc_msgsend() explicitement, ce que je prévois de faire. – Grimless