Donald Knuth a une citation très célèbre: «[P] Optimisation remature est la racine de tout mal"1. C'est une idée valable de garder dans votre tête tout en programmation. C'est en fait correct d'écrire du code inefficace à la fois. Dans le contexte de l'Objective-C, cela signifie que c'est correct d'utiliser des méthodes fournies et les objets de votre premier passage au algorithmes par écrit, même si vous savez que ces objets ou de méthodes pourraient ne pas être la meilleure façon de faire la tâche en termes de performances. Obtenir le code de travail, et le faire fonctionner à droite, puis revenez en arrière et d'optimiser intelligemment là où il sensé. Dans de nombreux endroits inefficacités petits n'auront aucun impact notable sur votre application, et en s'appuyant sur le code bien testé à partir de cacao qui a été écrit par Apple informés et ingénieurs Ensuite, vous vous probablement sauvé beaucoup de temps et des maux de tête d'entretien.
Mais, il ya des moments où le code inefficace a un impact et doit être abordée. En Java, Visual Basic, Ruby, et l'autre langages de haut niveau, il ya beaucoup à faire pour optimiser votre code, mais il ya une limite à ce que vous pouvez faire parce que ces langues vous isoler du fonctionnement interne à l'aide de mécanismes tels que collecte des ordures, Et une grande partie du code qui s'exécute lorsque vous exécutez votre code est complètement en dehors de votre contrôle. Dans toutes ces langues, vous pouvez contribuer à l'exécution savoir quand il est correct de libérer de la mémoire que vous avez fini avec elle en suivant de bonnes pratiques de codage, mais la gestion de la mémoire est surtout hors de vos mains. D'autres optimisations comme le déroulement de boucle peut être utilisée dans ces langues, mais le rendement de performance tend à être inférieur à des optimisations même dans un langage de bas niveau compilées directement en code machine.
Voici où Objective-C a vraiment une longueur d'avance. C'est un langage de haut niveau et a beaucoup en commun avec Java, VB, Et C#. Il a collecte des ordures (enfin, pas sur l'iPhone, pas encore) et la gestion des exceptions et il vous protège de nombreux détails d'implémentation de bas niveau comme un bon langage de haut niveau devrait. Mais, Objective-C est un ensemble super- C, Et même si elle a un petit moteur d'exécution pour activer la messagerie objet et d'autres fonctionnalités, la plupart de votre code se compile vers le langage machine. Vous êtes à l'abri des détails d'implémentation de bas niveau, mais pas empêchés d'obtenir pour eux. Lorsque vous avez épuisé vos options pour optimiser l'aide Objective-C et Cocoa, vous avez toujours la possibilité de lever le capot et l'utilisation de bons vieux C procédurales afin d'optimiser encore plus. Un grand Objective-C programmeur doit être d'au moins un bon programmeur C.
Jetons un oeil à un exemple d'optimisation d'un petit morceau de code Objective-C.
Disons que nous avons une NSData qui contient des données binaires codées stockées sous forme de caractères 8-bit ASCII. Dans ces données encodées, nous avons besoin de chercher certaines séquences de caractères pour s'assurer qu'il contient le type de données qu'il est censé le faire. Ces tags sont des séquences simples de caractères ASCII (alias C-cordes).
Voici ma première, la version écrite rapidement profit objets intégrés cacao. Ceci a été écrit dans le cadre d'une catégorie sur NSData, De sorte que toute référence à self est une référence à la NSData objet contenant les données binaires codées.
Je m'excuse pour le formatage du code. Blogger ne coopère pas et refuse de me croire quand je lui dis que mon code est préformaté en utilisant les pre tag.
L'original, horriblement non-Version optimisée
-(BOOL)containsCString:(char *)cmp
{
NSUInteger offset = 0;
char * byte;
const char * bytes = [self bytes];
while (offset {
byte = (char *)bytes ;
NSString * checkString = [NSString stringWithCString:byte length:strlen(cmp)];
if ([checkString isEqualToString:[NSString stringWithCString:cmp length:strlen(cmp)]])
return YES;
offset++;
}
return NO;
}
Dois-je le laisser comme un exercice pour l'étudiant d'identifier exactement pourquoi ce code est pauvre du point de vue d'optimisation? Je ferais mieux de ne pas, si les problèmes sont susceptibles évident pour beaucoup d'entre vous.
Tout d'abord, je suis l'allocation des objets autoreleased l'intérieur d'une boucle. Et cette boucle va octet par octet à travers un morceau potentiellement important de données. Aucun de la mémoire de ces objets seront libérés jusqu'à la fin de la passe de courant à travers la boucle courir, ce qui n'arrivera pas qu'après ma termine la boucle. J'ai créer deux autoreleased NSString cas pour chaque octet dans les données et ensuite exécuter une chaîne de comparer entre eux. NSString est un cluster de classe complexe, conçu pour faire face à une multitude d'encodages chaîne, et pourrait potentiellement être à l'aide caractères multi-octets pour représenter ces chaînes, ce qui signifie que chaque comparez pourrait être la comparaison de deux ou même trois octets plutôt qu'une seule. Maintenant, le NSString grappes de classe est intelligent et ne sera probablement pas l'utilisation multi-octets encodage à moins qu'il doit, mais depuis ce n'est pas notre code et les algorithmes ne sont pas documentées, nous ne pouvons pas savoir avec certitude ce qu'il va faire, et il est possible que quelque chose dans nos données pourraient déclencher l'utilisation de l'encodage multi-octets
Ainsi, fondamentalement, je suis d'être très, très inefficace à la fois avec la mémoire et la puissance de traitement ici. Cela peut ne pas être importante, selon la façon dont j'utilise dans mon application, et cette version ne m'a fallu que quelques minutes pour écrire, donc si je fais cela que rarement et les blocs de données codées sont petites, peut-être ce qui se passe d'être bien juste la façon dont il est. Mais il ya le potentiel pour des problèmes réels avec ce code dans une application.
Une façon de savoir si ça va être un vrai problème est de mettre en place des tests unitaires (vous utilisez des tests unitaires, non?) Qui utilisent des situations représentatives similaire à ce que le code sera confrontée à l'application effective.
Pour sourires et fous rires, nous allons exécuter mes tests unitaires sur cette méthode, qui prend deux à trois morceaux mégaoctet de données codées - celui qui contient les balises d'être recherché et qui n'a pas - et faire deux recherches sur les deux morceaux. Le résultat? 6,8 secondes à faire les deux recherches sur chacun des deux morceaux de trois mégaoctets qu'il fonctionne sur 2.6GHz aa MacBook Pro. C'est définitivement un potentiel Beachball Filature de la Mort situation. Je suppose que nous ferions mieux de chercher à optimiser, hein?
Notez que la quantité de temps qu'il faut pour exécuter ce code dépendra d'un certain nombre de facteurs, et il ne sera pas le même à chaque fois, mais il ne variait que de quelques millisecondes sur une douzaine de pistes, donc je suis assez confiant il est l'algorithme qui est lent et pas autre chose fonctionnant à la fois2.
Notez que la quantité de temps qu'il faut pour exécuter ce code dépendra d'un certain nombre de facteurs, et il ne sera pas le même à chaque fois, mais il ne variait que de quelques millisecondes sur une douzaine de pistes, donc je suis assez confiant il est l'algorithme qui est lent et pas autre chose fonctionnant à la fois2.
Optimisation d'abord
La première optimisation et la plus évidente ici devient l'affectation chaîne de comparaison hors de la boucle. Cette valeur ne change pas du tout pendant la boucle, nous allons donc passer à la déclaration avant que tout se passe de telle sorte que seulement une fois par appel à cette méthode. En théorie, qui devrait considérablement réduire les frais généraux de la mémoire et probablement de réduire le temps de traitement de manière significative. Voici la nouvelle version:
-(BOOL)containsCString:(char *)cmp
{
NSUInteger offset = 0;
char * byte;
const char * bytes = [self bytes];
NSString *compareString = [NSString stringWithCString:cmp length:strlen(cmp)];
while (offset {
byte = (char *)bytes ;
NSString * checkString = [NSString stringWithCString:byte length:strlen(cmp)];
if ([checkString isEqualToString:compareString])
return YES;
offset++;
}
return NO;
}
Lançons le test unitaire et voir ce qui se passe. Et le résultat? Cette fois-ci s'exécute dans un peu plus vite, pointant à 3,7 secondes. Une amélioration significative, mais pas assez vite pour éviter que les ballons de plage filer. Peut-être que je vais avoir à frayer les discussions. Je vais faire cet encodage beaucoup dans ma demande.
Alors, est-il un moyen que je peux améliorer cela davantage? Est-il possible pour moi d'éviter autoreleasing tant NSStrings, ou même les créer à tout? Eh bien, il ya un couple d'options. Je ne pouvais allouer manuellement et de libérer les objets chaîne. Ce serait beaucoup plus efficace que de remplir la piscine autorelease, en théorie.
La seconde optimisation
Voici ce que nous obtenons si nous allouer manuellement et la libération de nos instances NSString plutôt que de les laisser aller dans la piscine autorelease. Cela devrait aider avec nos frais généraux de la mémoire, mais on ignore combien d'une augmentation de vitesse, il va nous donner:
-(BOOL)containsCString:(char *)cmp
{
NSUInteger offset = 0;
char * byte;
const char * bytes = [self bytes];
NSString *compareString = [NSString stringWithCString:cmp length:strlen(cmp)];
while (offset {
byte = (char *)bytes ;
NSString *checkString = [[NSString alloc] initWithCString:byte length:strlen(cmp)];
if ([checkString isEqualToString:compareString])
return YES;
[checkString release];
offset++;
}
return NO;
}
Nous l'exécuter et? 3,44 secondes. Hm .. Une amélioration, mais pas un énorme par tout moyen. Il semble que la création de chaînes est un de nos blocages. Dans une méthode plus complexe, nous aurions probablement de recourir à l'aide de requin ou instruments permettant d'identifier les goulots d'étranglement ont été où notre, mais dans ce cas, c'est assez évident, où est le problème.
Donc, nous allons essayer de faire cela en droites C. Après tout, nous sommes simplement comparer des morceaux de mémoire. C peut faire ça, et il peut le faire rapidement sans la surcharge d'objets ou d'encodages de chaîne ou toute autre chose.
L'optimisation finale
Au lieu de cela, oublions l'aide NSString or NSMutableString, Et il suffit d'utiliser C. Nous allons travailler avec des pointeurs, et d'utiliser certaines techniques de la vieille école pour comparer les tampons.
-(BOOL)containsCString:(const char *)cmp
{
NSUInteger offset = 0;
const unsigned char * bytes = [self bytes];
while (offset {
if (memcmp(bytes++, cmp, strlen(cmp))==0)
return YES;
offset++;
}
return NO;
}
Cette version ne crée pas de nouveaux objets Objective-C, il utilise tout simplement une fonction C qui se compare à des morceaux de mémoire. Maintenant il n'ya pas de surcharge due à des objets, et aucune raison d'encodages de chaîne ou de conversions.
Alors, combien est-ce plus rapide? Lançons notre test unitaire et le découvrir. Comment environ 0,4 secondes? Pas terrible du tout pour faire une recherche d'octet par octet à 12 Mo de données, surtout quand la moitié des données qu'elle avait pour la recherche ne contient pas la chose qu'il cherchait.
Pensées finales
Nous pourrions aller plus loin. Il ya des optimisations que nous pouvions faire à ce qui consument plusieurs millisecondes plus, et si notre code nécessaire au traitement d'énormes blocs de données, qui serait probablement utile d'examiner (et peut-être une bonne idée pour une future affectation), mais pour ma fins, j'ai décidé que ce n'est une bonne performance suffisant pour mes besoins. Le point de cet article n'était pas vraiment pour vous enseigner la façon d'optimiser votre code, et n'est certainement pas de vous montrer la mise en œuvre plus rapide possible de cet algorithme, mais je voulais faire passer le point que l'Objective-C est C, et que, beau que tous les bienfaits orienté objet est, il ya des moments où il est logique de retrousser vos manches et de faire l'école de vieilles choses, une option que la plupart des langages de haut niveau ne proposent pas, mais l'Objective-C ne. Je voulais aussi vous de voir quelques-uns des types d'erreurs assez commun que vous voyez souvent dans les langages de haut niveau qui provoquent ballonnements et les goulets d'étranglement de mémoire afin que vous serez moins susceptible de les faire dans l'avenir.
Les timings dans cet article ont toutes été faites en utilisant la cible de débogage. L'optimisation finale chuté à 0,1 secondes en passant à la cible de presse, et j'ai été capable de le laisser tomber encore plus bas en mettant en œuvre un itérateur inverse pour la balise de fin.
1 - Programmation structurée avec de passer aux déclarations, Enquêtes ACM Computing Journal, vol 6, no 4, décembre 1974. p.268
2 - Il le fait, en effet, utile de savoir que j'ai intentionnellement écrit cet inefficacement.
Aucun commentaire:
Enregistrer un commentaire