Nous savons tous que l'optimisation prématurée est la racine de tout mal, car elle conduit à un code illisible/non maintenable. La pessimisation est encore pire, quand quelqu'un implémente une "optimisation" parce qu'il pensez ce sera plus rapide, mais cela finit par être plus lent, ainsi que bogué, non maintenable, etc. Quel est l'exemple le plus ridicule de cela que vous avez vu?
Sur un ancien projet, nous avons hérité de certains (sinon excellents) programmeurs de systèmes embarqués qui avaient une grande expérience du Z-8000.
Notre nouvel environnement était Sparc Solaris 32 bits.
L'un des gars est allé et a changé tous les entiers en short pour accélérer notre code, car récupérer 16 bits de RAM était plus rapide que de saisir 32 bits.
J'ai dû écrire un programme de démonstration pour montrer que la capture de valeurs 32 bits sur un système 32 bits était plus rapide que la capture de valeurs 16 bits, et expliquer que pour saisir une valeur 16 bits, le CPU devait faire une largeur 32 bits accéder à la mémoire, puis masquer ou décaler les bits non nécessaires pour la valeur 16 bits.
Je pense que l'expression "l'optimisation prématurée est la racine de tout mal" est très utilisée. Pour de nombreux projets, c'est devenu une excuse pour ne prendre en compte les performances que tard dans un projet.
Cette phrase est souvent une béquille pour que les gens évitent le travail. Je vois cette expression utilisée quand les gens devraient vraiment dire "Gee, nous n'y avons vraiment pas pensé à l'avance et n'avons pas le temps de nous en occuper maintenant".
J'ai vu beaucoup plus d'exemples "ridicules" de problèmes de performances stupides que d'exemples de problèmes introduits en raison de la "pessimisation"
Ce que je pense être une meilleure déclaration est la suivante: "l'optimisation sans mesurer et comprendre n'est pas du tout l'optimisation - c'est juste un changement aléatoire".
Un bon travail de performance prend du temps - souvent plus que le développement de la fonctionnalité ou du composant lui-même.
Les bases de données sont des terrains de jeux de pessimisation.
Les favoris incluent:
C'est du haut de ma tête.
Je pense qu'il n'y a pas de règle absolue: certaines choses sont optimisées au départ, d'autres pas.
Par exemple, j'ai travaillé dans une entreprise où nous recevions des paquets de données de satellites. Chaque paquet coûtait beaucoup d'argent, donc toutes les données étaient hautement optimisées (c'est-à-dire emballées). Par exemple, la latitude/longitude n'a pas été envoyée sous forme de valeurs absolues (flottants), mais sous forme de décalages par rapport au coin "nord-ouest" d'une zone "actuelle". Nous avons dû déballer toutes les données avant de pouvoir les utiliser. Mais je pense que ce n'est pas de la pessimisation, c'est de l'optimisation intelligente pour réduire les coûts de communication.
D'autre part, nos architectes logiciels ont décidé que les données décompressées devaient être formatées dans un document XML très lisible et stockées dans notre base de données en tant que telles (par opposition à avoir chaque champ stocké dans une colonne correspondante). Leur idée était que "XML est le futur", "l'espace disque est bon marché" et "le processeur est bon marché", il n'était donc pas nécessaire d'optimiser quoi que ce soit. Le résultat a été que nos paquets de 16 octets ont été transformés en documents de 2 Ko stockés dans une colonne, et pour des requêtes même simples, nous avons dû charger des mégaoctets de documents XML en mémoire! Nous avons reçu plus de 50 paquets par seconde, vous pouvez donc imaginer à quel point la performance est devenue horrible (BTW, la société a fait faillite).
Encore une fois, il n'y a pas de règle absolue. Oui, parfois l'optimisation trop tôt est une erreur. Mais parfois, la devise "CPU/espace disque/mémoire est bon marché" est la véritable racine de tous les maux.
Oh mon Dieu, je pense que je les ai tous vus. Plus souvent qu'autrement, c'est un effort pour résoudre les problèmes de performances par quelqu'un qui est sacrément paresseux pour résoudre leur problème jusqu'à la CAUSE de ces problèmes de performances ou même rechercher s'il existe réellement IS un problème de performances Dans beaucoup de ces cas, je me demande si ce n'est pas seulement le cas de cette personne qui veut essayer une technologie particulière et qui cherche désespérément un clou qui correspond à son nouveau marteau brillant.
Voici un exemple récent:
L'architecte de données vient à moi avec une proposition élaborée de partitionner verticalement une table de clés dans une application assez grande et complexe. Il veut savoir quel type d'effort de développement serait nécessaire pour s'adapter au changement. La conversation s'est déroulée comme suit:
Moi: Pourquoi envisagez-vous cela? Quel est le problème que vous essayez de résoudre?
Lui: Le tableau X est trop large, nous le partitionnons pour des raisons de performances.
Moi: Qu'est-ce qui vous fait penser qu'il est trop large?
Lui: Le consultant a dit que c'était trop de colonnes pour avoir dans une table.
Moi: Et cela affecte les performances?
Lui: Oui, les utilisateurs ont signalé des ralentissements intermittents dans le module XYZ de l'application.
Moi: Comment savez-vous que la largeur de la table est la source du problème?
Lui: C'est le tableau des clés utilisé par le module XYZ, et c'est comme 200 colonnes. Ce doit être le problème.
Moi (Explication): Mais le module XYZ en particulier utilise la plupart des colonnes de cette table, et les colonnes qu'il utilise sont imprévisibles car l'utilisateur configure l'application pour afficher les données qu'il souhaite afficher à partir de cette table . Il est probable que 95% du temps, nous finirions par réunir toutes les tables ensemble, ce qui nuirait aux performances.
Lui: Le consultant a dit qu'il est trop large et que nous devons le changer.
Moi: Qui est ce consultant? Je ne savais pas que nous avions engagé un consultant, et ils n'ont pas du tout parlé à l'équipe de développement.
Lui: Eh bien, nous ne les avons pas encore embauchés. Cela fait partie d'une proposition qu'ils ont proposée, mais ils ont insisté sur le fait que nous devions ré-architecturer cette base de données.
Moi: Uh huh. Ainsi, le consultant qui vend des services de refonte de base de données pense que nous avons besoin d'une refonte de base de données ...
La conversation s'est poursuivie indéfiniment comme ça. Par la suite, j'ai jeté un nouveau coup d'œil au tableau en question et j'ai déterminé qu'il pourrait probablement être réduit avec une simple normalisation sans avoir besoin de stratégies de partitionnement exotiques. Bien sûr, cela s'est avéré être un point discutable une fois que j'ai enquêté sur les problèmes de performances (auparavant non signalés) et les ai décelés à deux facteurs:
Bien sûr, l'architecte continue de pousser pour une partition verticale de la table accrochée au méta-problème "trop large". Il a même renforcé son dossier en obtenant une proposition d'un autre consultant en base de données qui a pu déterminer que nous avions besoin de modifications majeures de la conception de la base de données sans regarder l'application ou exécuter une analyse des performances.
J'ai vu des gens utiliser alphadrive-7 pour incuber totalement CHX-LT. C'est une pratique peu courante. La pratique la plus courante consiste à initialiser le transformateur ZT de manière à réduire la mise en mémoire tampon (en raison d'une plus grande résistance aux surcharges nettes) et à créer Java style bytegraphications).
Totalement pessimiste!
Rien de bouleversant, je l'avoue, mais j'ai surpris des gens utilisant StringBuffer pour concaténer des chaînes en dehors d'une boucle en Java. C'était quelque chose de simple comme tourner
String msg = "Count = " + count + " of " + total + ".";
dans
StringBuffer sb = new StringBuffer("Count = ");
sb.append(count);
sb.append(" of ");
sb.append(total);
sb.append(".");
String msg = sb.toString();
Avant, c'était une pratique assez courante d'utiliser la technique en boucle, car c'était beaucoup plus rapide. Le truc, c'est que StringBuffer est synchronisé, donc il y a en fait des frais supplémentaires si vous ne concaténez que quelques chaînes. (Sans oublier que la différence est absolument négligeable à cette échelle.) Deux autres points à propos de cette pratique:
J'ai vu une fois une base de données MSSQL qui utilisait une table 'Root'. La table racine avait quatre colonnes: GUID (identifiant unique), ID (int), LastModDate (datetime) et CreateDate (datetime). Toutes les tables de la base de données étaient à clé étrangère à la racine Chaque fois qu'une nouvelle ligne a été créée dans la table any dans la base de données, vous avez dû utiliser quelques procédures stockées pour insérer une entrée dans la table racine avant de pouvoir accéder à la table réelle dont vous vous souciez. (plutôt que la base de données faisant le travail pour vous avec quelques déclencheurs simples déclencheurs).
Cela a créé un gâchis de maux de tête et de maux de tête inutiles, a exigé que tout ce qui est écrit dessus utilise des sprocs (et élimine mes espoirs d'introduire LINQ dans l'entreprise. t même accomplir ce qu'il était censé faire.
Le développeur qui a choisi ce chemin l'a défendu en supposant que cela économisait des tonnes d'espace parce que nous n'utilisions pas les guides sur les tables elles-mêmes (mais ... n'est pas un GUID généré dans le Table racine pour chaque ligne que nous créons?), A amélioré les performances d'une manière ou d'une autre et a rendu "facile" l'audit des modifications apportées à la base de données.
Oh, et le diagramme de la base de données ressemblait à une araignée mutante de l'enfer.
Que diriez-vous POBI - pessimisation évidemment par intention?
Un de mes collègues dans les années 90 était fatigué de se faire botter le cul par le PDG juste parce que le PDG passait le premier jour de chaque ERP version logicielle (une version personnalisée) avec localisation des problèmes de performances dans le nouvelles fonctionnalités. Même si les nouvelles fonctionnalités crunch gigaoctets et rendu l'impossible possible, il a toujours trouvé quelques détails, ou même un problème apparemment majeur, à pleurnicher. Il croyait en savoir beaucoup sur la programmation et a obtenu ses coups de pied en donnant un coup de pied aux ânes du programmeur.
En raison de la nature incompétente de la critique (il était PDG, pas informaticien), mon collègue n'a jamais réussi à bien faire les choses. Si vous n'avez pas de problème de performances, vous ne pouvez pas l'éliminer ...
Jusqu'à une version, il a mis beaucoup d'appels de fonction Delay (200) (c'était Delphi) dans le nouveau code. Cela n'a pris que 20 minutes après la mise en ligne, et il a été sommé de comparaître dans le bureau du PDG pour aller chercher ses insultes en retard.
La seule chose inhabituelle jusqu'à présent était que mes collègues soient muets quand il est revenu, souriant, plaisantant, sortant pour un BigMac ou deux alors qu'il normalement donnait un coup de pied aux tables, flambait sur le PDG et la société, et passait le reste de la journée à la mort .
Naturellement, mon collègue s'est maintenant reposé pendant un ou deux jours à son bureau, améliorant ses compétences de visée dans Quake - puis le deuxième ou le troisième jour, il a supprimé les appels de retard, reconstruit et publié un "patch d'urgence" dont il a fait passer le mot. qu'il avait passé 2 jours et 1 nuit pour réparer les trous de performance.
C'était la première (et la seule) fois que le PDG maléfique disait "excellent travail!" à lui. C'est tout ce qui compte, non?
C'était du vrai POBI.
Mais c'est aussi une sorte d'optimisation des processus sociaux, donc c'est 100% ok.
Je pense.
"Indépendance de la base de données". Cela signifiait aucun proc stocké, déclencheurs, etc. - pas même aucune clé étrangère.
var stringBuilder = new StringBuilder();
stringBuilder.Append(myObj.a + myObj.b + myObj.c + myObj.d);
string cat = stringBuilder.ToString();
Meilleure utilisation d'un StringBuilder que j'ai jamais vue.
Utilisation d'une expression régulière pour fractionner une chaîne lorsqu'une simple chaîne.split suffit
Très tard pour ce fil, je sais, mais je l'ai vu récemment:
bool isFinished = GetIsFinished();
switch (isFinished)
{
case true:
DoFinish();
break;
case false:
DoNextStep();
break;
default:
DoNextStep();
}
Vous savez, juste au cas où un booléen aurait des valeurs supplémentaires ...
Le pire exemple auquel je puisse penser est une base de données interne à mon entreprise contenant des informations sur tous les employés. Il reçoit une mise à jour nocturne des RH et dispose d'un service Web ASP.NET au-dessus. De nombreuses autres applications utilisent le service Web pour remplir des éléments tels que les champs de recherche/liste déroulante.
Le pessimisme est que le développeur pensait que les appels répétés au service Web seraient trop lents pour effectuer des requêtes SQL répétées. Alors, qu'est ce qu'il a fait? L'événement de démarrage d'application lit dans la base de données entière et convertit le tout en objets en mémoire, stockés indéfiniment jusqu'à ce que le pool d'applications soit recyclé. Ce code était si lent qu'il faudrait 15 minutes pour charger moins de 2000 employés. Si vous avez recyclé par inadvertance le pool d'applications pendant la journée, cela pourrait prendre 30 minutes ou plus, car chaque demande de service Web démarrerait plusieurs rechargements simultanés. Pour cette raison, les nouvelles recrues n'apparaîtraient pas dans la base de données le premier jour de la création de leur compte et ne pourraient donc pas accéder à la plupart des applications internes au cours de leurs deux premiers jours, se tortillant les pouces.
Le deuxième niveau de pessimisme est que le directeur du développement ne veut pas y toucher par crainte de casser les applications dépendantes, mais nous continuons cependant à avoir des pannes sporadiques à l'échelle de l'entreprise des applications critiques en raison de la mauvaise conception d'un composant aussi simple.
Personne ne semble avoir mentionné le tri, alors je le ferai.
Plusieurs fois, j'ai découvert que quelqu'un avait fabriqué à la main un tri de bulles, car la situation "ne nécessitait pas" un appel à l'algorithme de tri rapide "trop sophistiqué" qui existait déjà. Le développeur a été satisfait lorsque leur bulle artisanale a fonctionné assez bien sur les dix lignes de données qu'ils utilisent pour les tests. Cela ne s'est pas aussi bien passé après que le client ait ajouté quelques milliers de lignes.
J'ai dû essayer de modifier le code qui incluait ces gemmes dans la classe Constants
public static String COMMA_DELIMINATOR=",";
public static String COMMA_SPACE_DELIMINATOR=", ";
public static String COLIN_DELIMINATOR=":";
Chacun d'eux a été utilisé plusieurs fois dans le reste de l'application à des fins différentes. COMMA_DELIMINATOR a jonché le code avec plus de 200 utilisations dans 8 packages différents.
Le grand numéro un de tous les temps que je rencontre maintes et maintes fois dans les logiciels internes:
Ne pas utiliser les fonctionnalités du SGBD pour des raisons de "portabilité" car "nous pourrions vouloir passer à un autre fournisseur plus tard".
Lis sur mes lèvres. Pour tout travail en interne: il ne se passera pas!
J'ai déjà travaillé sur une application pleine de code comme celle-ci:
1 Tuple *FindTuple( DataSet *set, int target ) {
2 Tuple *found = null;
3 Tuple *curr = GetFirstTupleOfSet(set);
4 while (curr) {
5 if (curr->id == target)
6 found = curr;
7 curr = GetNextTuple(curr);
8 }
9 return found;
10 }
Il suffit de supprimer found
, de retourner null
à la fin et de changer la sixième ligne en:
return curr;
Double les performances de l'application.
J'avais un collègue qui essayait de déjouer l'optimiseur de notre compilateur C et le code de réécriture de routine que lui seul pouvait lire. L'une de ses astuces préférées consistait à changer une méthode lisible comme (inventer du code):
int some_method(int input1, int input2) {
int x;
if (input1 == -1) {
return 0;
}
if (input1 == input2) {
return input1;
}
... a long expression here ...
return x;
}
en cela:
int some_method() {
return (input == -1) ? 0 : (input1 == input2) ? input 1 :
... a long expression ...
... a long expression ...
... a long expression ...
}
Autrement dit, la première ligne d'une méthode une fois lisible deviendrait "return
" et toute autre logique serait remplacée par des expressions terniaires profondément imbriquées. Lorsque vous avez essayé de discuter de la façon dont cela était impossible à maintenir, il a souligné le fait que le résultat de l'Assemblée de sa méthode était trois ou quatre instructions de l'Assemblée plus courtes. Ce n'était pas nécessairement plus rapide mais c'était toujours minuscule un peu plus court. Il s'agissait d'un système embarqué où l'utilisation de la mémoire était parfois importante, mais il y avait des optimisations beaucoup plus faciles qui auraient pu être faites que cela aurait laissé le code lisible.
Puis, après cela, pour une raison quelconque, il a décidé que ptr->structElement
était trop illisible, alors il a commencé à changer tout cela en (*ptr).structElement
sur la théorie qu'il était également plus lisible et plus rapide.
Transformer du code lisible en code illisible pour au plus une amélioration de 1%, et parfois en fait un code plus lent.
Dans l'un de mes premiers emplois en tant que développeur à part entière, j'ai repris un projet pour un programme qui souffrait de problèmes de mise à l'échelle. Il fonctionnerait raisonnablement bien sur de petits ensembles de données, mais se bloquerait complètement lorsqu'il recevrait de grandes quantités de données.
En creusant, j'ai trouvé que le programmeur d'origine cherchait à accélérer les choses en parallélisant l'analyse - en lançant un nouveau thread pour chaque source de données supplémentaire. Cependant, il avait fait une erreur en ce que tous les threads nécessitaient une ressource partagée, sur laquelle ils étaient bloqués. Bien sûr, tous les avantages de la simultanéité ont disparu. De plus, la plupart des systèmes se sont écrasés pour lancer plus de 100 threads uniquement pour que tous, sauf un, se verrouillent. Ma machine de développement costaud était une exception dans la mesure où elle tournait à travers un ensemble de données de 150 sources en environ 6 heures.
Donc, pour y remédier, j'ai supprimé les composants multithread et nettoyé les E/S. En l'absence d'autres modifications, le temps d'exécution sur l'ensemble de données de 150 sources est passé en dessous de 10 minutes sur ma machine, et de l'infini à moins d'une demi-heure sur la machine moyenne de l'entreprise.
Je suppose que je pourrais offrir ce joyau:
unsigned long isqrt(unsigned long value)
{
unsigned long tmp = 1, root = 0;
#define ISQRT_INNER(shift) \
{ \
if (value >= (tmp = ((root << 1) + (1 << (shift))) << (shift))) \
{ \
root += 1 << shift; \
value -= tmp; \
} \
}
// Find out how many bytes our value uses
// so we don't do any uneeded work.
if (value & 0xffff0000)
{
if ((value & 0xff000000) == 0)
tmp = 3;
else
tmp = 4;
}
else if (value & 0x0000ff00)
tmp = 2;
switch (tmp)
{
case 4:
ISQRT_INNER(15);
ISQRT_INNER(14);
ISQRT_INNER(13);
ISQRT_INNER(12);
case 3:
ISQRT_INNER(11);
ISQRT_INNER(10);
ISQRT_INNER( 9);
ISQRT_INNER( 8);
case 2:
ISQRT_INNER( 7);
ISQRT_INNER( 6);
ISQRT_INNER( 5);
ISQRT_INNER( 4);
case 1:
ISQRT_INNER( 3);
ISQRT_INNER( 2);
ISQRT_INNER( 1);
ISQRT_INNER( 0);
}
#undef ISQRT_INNER
return root;
}
Comme la racine carrée a été calculée à un endroit très sensible, j'ai eu la tâche de chercher un moyen de la rendre plus rapide. Ce petit refactoring a réduit le temps d'exécution d'un tiers (pour la combinaison du matériel et du compilateur utilisés, YMMV):
unsigned long isqrt(unsigned long value)
{
unsigned long tmp = 1, root = 0;
#define ISQRT_INNER(shift) \
{ \
if (value >= (tmp = ((root << 1) + (1 << (shift))) << (shift))) \
{ \
root += 1 << shift; \
value -= tmp; \
} \
}
ISQRT_INNER (15);
ISQRT_INNER (14);
ISQRT_INNER (13);
ISQRT_INNER (12);
ISQRT_INNER (11);
ISQRT_INNER (10);
ISQRT_INNER ( 9);
ISQRT_INNER ( 8);
ISQRT_INNER ( 7);
ISQRT_INNER ( 6);
ISQRT_INNER ( 5);
ISQRT_INNER ( 4);
ISQRT_INNER ( 3);
ISQRT_INNER ( 2);
ISQRT_INNER ( 1);
ISQRT_INNER ( 0);
#undef ISQRT_INNER
return root;
}
Bien sûr, il existe à la fois des moyens plus rapides ET meilleurs pour le faire, mais je pense que c'est un bel exemple de pessimisation.
Edit: à bien y penser, la boucle déroulée était en fait aussi une pessimisation soignée. En creusant à travers le contrôle de version, je peux également présenter la deuxième étape de la refactorisation, qui a été encore meilleure que la précédente:
unsigned long isqrt(unsigned long value)
{
unsigned long tmp = 1 << 30, root = 0;
while (tmp != 0)
{
if (value >= root + tmp) {
value -= root + tmp;
root += tmp << 1;
}
root >>= 1;
tmp >>= 2;
}
return root;
}
C'est exactement le même algorithme, bien qu'une implémentation légèrement différente, donc je suppose qu'il est admissible.
Cela pourrait être à un niveau supérieur à ce que vous recherchiez, mais le corriger (si vous êtes autorisé) implique également un niveau de douleur plus élevé:
Insister sur le fait de faire rouler un Object Relationship Manager/Data Access Layer au lieu d'utiliser l'une des bibliothèques établies, testées et matures (même après qu'elles vous ont été signalées).
Toutes les contraintes de clé étrangère ont été supprimées d'une base de données, sinon il y aurait tellement d'erreurs.
Cela ne correspond pas exactement à la question, mais je vais quand même le mentionner comme un récit édifiant. Je travaillais sur une application distribuée qui fonctionnait lentement et s'est envolée vers DC pour participer à une réunion visant principalement à résoudre le problème. Le chef de projet a commencé à esquisser une ré-architecture visant à résoudre le retard. Je me suis porté volontaire pour avoir pris quelques mesures au cours du week-end qui ont isolé le goulot d'étranglement à une seule méthode. Il s'est avéré qu'il y avait un enregistrement manquant sur une recherche locale, obligeant l'application à aller sur un serveur distant chaque transaction. En ajoutant le dossier au magasin local, le retard a été éliminé - problème résolu. Notez que la ré-architecture n'aurait pas résolu le problème.
Vérifier avant CHAQUE opération javascript si l'objet sur lequel vous opérez existe.
if (myObj) { //or its evil cousin, if (myObj != null) {
label.text = myObj.value;
// we know label exists because it has already been
// checked in a big if block somewhere at the top
}
Mon problème avec ce type de code est que personne ne semble se soucier s'il n'existe pas? Ne fais rien? Ne donnez pas le feedback à l'utilisateur?
J'accepte que le Object expected
les erreurs sont ennuyeuses, mais ce n'est pas la meilleure solution pour cela.
Que diriez-vous de l'extrémisme YAGNI. C'est une forme de pessimisation prématurée. Il semble que chaque fois que vous appliquez YAGNI, vous finissez par en avoir besoin, ce qui entraîne 10 fois plus d'efforts pour l'ajouter que si vous l'aviez ajouté au début. Si vous créez un programme réussi, les chances sont que VOUS ALLEZ EN BESOIN. Si vous avez l'habitude de créer des programmes dont la vie s'épuise rapidement, continuez à pratiquer YAGNI car je suppose que YAGNI.
Pas exactement une optimisation prématurée - mais certainement erronée - cela a été lu sur le site Web de la BBC, à partir d'un article sur Windows 7.
M. Curran a déclaré que l'équipe de Microsoft Windows avait examiné tous les aspects du système d'exploitation pour apporter des améliorations. "Nous avons pu réduire de 400 millisecondes le temps d'arrêt en réduisant légèrement la musique d'arrêt du fichier WAV.
Maintenant, je n'ai pas encore essayé Windows 7, donc je peux me tromper, mais je suis prêt à parier qu'il y a d'autres problèmes qui sont plus importants que le temps qu'il faut pour arrêter. Après tout, une fois que je vois le message "Arrêter Windows", le moniteur est éteint et je m'en vais - en quoi ces 400 millisecondes me sont-elles profitables?
Quelqu'un dans mon département a écrit une fois une classe de cordes. Une interface comme CString
, mais sans la dépendance Windows.
Une "optimisation" qu'ils ont faite a consisté à pas allouer plus de mémoire que nécessaire. Apparemment, ne réalisant pas que les classes de raison comme std::string
ne pas allouer de mémoire excédentaire est telle qu'une séquence de +=
les opérations peuvent s'exécuter en O(n) fois.
Au lieu de cela, chaque single +=
call a forcé une réallocation, qui a transformé les appendices répétés en O (n²) algorithme de Schlemiel le peintre .
Un de mes anciens collègues (un soab , en fait) a été affecté à la construction d'un nouveau module pour notre Java ERP qui devrait ont collecté et analysé les données des clients (commerce de détail). Il a décidé de diviser CHAQUE champ Calendrier/Datetime en ses composants (secondes, minutes, heures, jour, mois, année, jour de la semaine, bimester, trimestre (!)) parce que " sinon, comment pourrais-je demander "tous les lundis"? "
Je ne pense pas que la pessimisation soit rare. D'après mon expérience dans l'optimisation des performances, une grande partie des mauvaises performances est causée par de "bonnes pratiques de programmation" justifiées au nom de "l'efficacité". Exemples:
Collections de cartes ou "dictionnaires"
Ceux-ci utilisent généralement une sorte de codage de hachage, de sorte qu'ils auront des performances O(1), mais ne seront rentables que lorsqu'ils seront remplis avec beaucoup plus d'éléments que ceux généralement utilisés.
Itérateurs
Ceux-ci sont justifiés comme étant éventuellement optimisés en un code en ligne efficace, alors qu'il est rarement vérifié pour voir s'ils le sont réellement.
Notifications et gestion des événements comme moyen de garder les données cohérentes
. Cependant, il existe une grande différence entre immédiateté et efficacité. De plus, les "propriétés", lors de l'obtention ou de la définition, sont encouragées à pénétrer profondément dans la structure de données pour essayer de la garder cohérente. Ces méthodes de "laisse courte" peuvent entraîner de grandes quantités de calculs inutiles. Les méthodes de "laisse longue", comme le fait de parcourir périodiquement la structure de données pour la "réparer", peuvent être un peu moins "immédiates" mais beaucoup plus efficaces.
Peut-être qu'un simple coup d'œil rapide sur le système vous aidera à identifier les goulots d'étranglement possibles.
"Cette partie n'a pas besoin d'être rapide" (archivage des journaux) "Cette partie doit être très rapide" (accepter de nouvelles connexions)
Ensuite, les pièces très rapides n'ont généralement pas besoin d'être optimisées avec des bizarreries sales, un matériel généralement décent et des pièces bien codées suffiront.
Je réponds simplement à la simple question "Est-ce que je gagne quelque chose à avoir cette partie de code très rapidement?" sera une excellente ligne directrice. Je veux dire, utiliser le bon sens optimise les autres parties du projet!
while true; do echo 3 > /proc/sys/vm/drop_caches; sleep 3600; done
Cela a amené le noyau à passer du temps à vider le cache disque, et une fois qu'il a réussi, tout s'est déroulé lentement jusqu'à ce que le cache soit repeuplé. Causé par une mauvaise compréhension du fait que le cache disque empêchait la mémoire d'être disponible pour les applications à utiliser.
N'offense personne, mais je viens de noter une tâche (Java) qui avait cette
import Java.lang.*;
Une autre astuce de performance fantaisiste :)
if (!loadFromDb().isEmpty) {
resultList = loadFromDb();
// do something with results
}
Pour un petit prix de hit DB supplémentaire, vous économisez tout ce temps en faisant comme 10 lignes de code, ce qui ne ferait probablement pas grand-chose de toute façon. Et des choses comme ça étaient dispersées partout dans le code :)
Une entreprise que j'ai visitée en tant que consultant il y a plusieurs années avait écrit une fonction de tri qui ressemblait à ceci:
procedure sort(string[] values, string direction)
begin
while not sorted do
begin
for every value in values
begin
if direction="Ascending" then
begin
... swap values in ascending order
end
else if direction="Descending" then
begin
... swap values in descending order
end
end;
end;
end;
C'est un algorithme de bullesort (qui est inefficace en soi) avec un comparaison de chaînes pour la direction dans la boucle intérieure! Je pouvais à peine en croire mes yeux et expliqué que oui, je peux probablement apporter quelques améliorations de vitesse ici (ils étaient mes clients après tout, donc je devais être diplomate sur le fait que c'était le code le plus non optimal que j'aie jamais vu :-))
Un collègue a dû vérifier l'accès à la page pour un rôle spécifique - "Admin" uniquement. Voici ce qu'elle a écrit:
.
if( CurrentUser.CurrentRole == "Role1" || CurrentUser.CurrentRole == "Role2")
{
// Access denied
}
else
{
// Access granted
}
au lieu de
if( !CurrentUser.CurrentRole.equals("Admin") )
{
// access denied
}
Ainsi, chaque fois qu'un nouveau rôle était ajouté au système, le nouveau rôle avait accès à toutes les pages confidentielles.
Le même collègue a également été joint pour la table de production et d'archivage pour toutes les requêtes.
Certains de mes collègues, qui étaient sur un projet "d'optimisation" de lots côté serveur existants (écrits en C++), "ont optimisé" à mort la classe de journalisation (!), En utilisant du code et des fonctions spécifiques à win32.
Peut-être que le goulot d'étranglement était dans logger.write (...), qui sait ...
Beaucoup de programmeurs ne connaissent pas ou ne veulent pas connaître SQL, ils trouvent donc des "trucs" pour éviter d'utiliser vraiment SQL afin de pouvoir placer les données dans un tableau. Les tableaux rendent certaines personnes heureuses. (J'adore les curseurs et les tableaux. Coke et Pepsi.) J'ai trouvé ces deux blocs de code dans le code de quelques programmeurs orientés objet qui se plaignaient de la lenteur des bases de données relationnelles. (la réponse n'est pas plus de mémoire ni plus de processeurs.)
Dans ce cas, la table est une énorme table avec le uniqueid_col est un identifiant unique ou une ligne unique.
Chargez ces données dans arrayX (car les tableaux doivent être plus rapides)
Sélectionnez uniqueid_col, col2, col3 Dans super_big_tbl
(code pseudo)
Loop
arrayX.next_record
if uniqueid_col = '829-39-3984'
return col2
end if
end loop
(Ma réponse est en bas.)
Cette prochaine est une simple erreur que j'ai également vue. L'idée est que vous n'obtenez jamais un double de cette façon:
Sélectionnez uniqueid_col, col2, col3 Dans le groupe super_big_tbl Par uniqueid_col, col2, col3 Ayant uniqueid_col = '829-39-3984'
La syntaxe correcte doit être
Sélectionnez uniqueid_col, col2, col3 Dans super_big_tbl Où uniqueid_col = '829-39-3984'
J'allais mentionner StringBuilder pour les concats de chaîne minuscules/sans boucle, mais cela a été mentionné.
Placer les variables d'une méthode dans des membres de classe privés pour les empêcher d'obtenir "des ordures collectées à chaque exécution de la méthode". Les variables sont des types de valeur.
Une application qui a utilisé un champ Entier pour allouer au niveau du bit à quels groupes d'accès à l'application nos clients pourraient ajouter leurs utilisateurs. Cela signifiait à l'époque que nous pouvions créer un grand total de 32 groupes à partager entre les 500+ clients.
Aaaah, mais une comparaison au niveau du bit est plus rapide qu'un égal et waaay plus rapide qu'une jointure, n'est-ce pas?
Malheureusement, quand j'ai complètement (et plutôt vocalement) flippé à ce code et à son auteur, j'ai découvert que l'auteur était mon patron. Un mec plutôt autoritaire, il s'avère.
P.s.
Je sais à quoi tu penses, ça aurait dû être une chaîne binaire non? :)
J'en ai un intentionnel ... J'ai déjà implémenté le tri par retour en arrière ... juste comme une preuve de concept;)) inutile de dire que ses performances étaient horribles.
Tout effort d'optimisation significatif qui n'est pas basé sur des rapports triés à partir d'un outil de profilage gagne un gros WTF de ma part.