performance unity optimization - Quelle est la pessimisation la plus ridicule que vous ayez vue?




15 Answers

Sur un ancien projet, nous avons hérité de programmeurs de systèmes embarqués (excellents) qui avaient une expérience massive de Z-8000.

Notre nouvel environnement était Sparc Solaris 32 bits.

Un des gars est allé et a tout changé en short pour accélérer notre code, car saisir 16 bits de RAM était plus rapide que d'en saisir 32 bits.

J'ai dû écrire un programme de démonstration pour montrer que saisir des valeurs 32 bits sur un système 32 bits était plus rapide que saisir des valeurs 16 bits, et expliquer que pour obtenir une valeur de 16 bits, le processeur devait faire 32 bits de large l'accès à la mémoire, puis masquer ou décaler les bits non nécessaires pour la valeur de 16 bits.

tuning belgique

Nous savons tous que l'optimisation prématurée est la racine de tous les maux, car elle conduit à un code illisible / non supportable. Pire encore, quand quelqu'un met en place une «optimisation» parce qu'elle pense que ce sera plus rapide, mais cela finit par être plus lent, en étant bogué, non supportable, etc. Quel est l'exemple le plus ridicule que vous avez vu? ?




Les bases de données sont Playland pessimization.

Les favoris incluent:

  • Divisez un tableau en multiples (par période, par ordre alphabétique, etc.) car c'est "trop ​​grand".
  • Créez une table d'archivage pour les enregistrements supprimés, mais continuez à l'associer à la table de production.
  • Dupliquer des bases de données complètes par (division / client / produit / etc.)
  • Résistez à ajouter des colonnes à un index, car cela le rend trop grand.
  • Créez beaucoup de tableaux récapitulatifs car le recalcul des données brutes est trop lent.
  • Créez des colonnes avec des sous-champs pour économiser de l'espace.
  • Dénormaliser en champs-comme-tableau.

C'est du haut de ma tête.




Oh mon Dieu, je pense que je les ai tous vus. Plus souvent qu'autrement, il s'agit d'un effort pour résoudre les problèmes de performance par quelqu'un qui est trop paresseux pour résoudre la cause de ces problèmes de performance ou même rechercher s'il existe réellement un problème de performance. Dans plusieurs de ces cas, je me demande si ce n'est pas le cas de cette personne qui veut essayer une technologie particulière et qui cherche désespérément un clou adapté à son nouveau marteau.

Voici un exemple récent:

L'architecte de données vient à moi avec une proposition élaborée pour partitionner verticalement une table de clé 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 ceci:

Moi: Pourquoi considérez-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 performance.

Moi: Qu'est - ce qui te fait penser que c'est trop large?

Lui: Le consultant a dit que c'est trop de colonnes à avoir dans une table.

Moi: Et cela affecte la performance?

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 la table clé utilisée par le module XYZ, et c'est comme 200 colonnes. Ça doit être le problème.

Moi (Explication): Mais le module XYZ utilise en particulier la plupart des colonnes de ce tableau, et les colonnes qu'il utilise sont imprévisibles car l'utilisateur configure l'application pour afficher les données qu'elle veut afficher depuis cette table. Il est probable que 95% du temps, nous finirions par rejoindre toutes les tables, ce qui nuirait à la performance.

Lui: Le consultant a dit que c'était trop large et que nous devions le changer.

Moi: Qui est ce consultant? Je ne savais pas que nous avions embauché un consultant, ni 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éorganiser cette base de données.

Moi: Euh hein. Ainsi, le consultant qui vend des services de re-conception de base de données pense que nous avons besoin d'une re-conception de base de données ....

La conversation continuait comme ça. Par la suite, j'ai jeté un autre coup d'œil sur la table en question et j'ai déterminé qu'elle pourrait probablement être restreinte à une simple normalisation sans avoir besoin de stratégies de partitionnement exotiques. Ceci, bien sûr s'est avéré être un point discutable une fois que j'ai étudié les problèmes de performance (précédemment non signalés) et les traquait à deux facteurs:

  1. Indices manquants sur quelques colonnes clés.
  2. Quelques analystes de données malveillants qui bloquaient périodiquement les tables de clés (y compris la table «trop large») en interrogeant directement la base de données de production avec MSAccess.

Bien sûr, l'architecte est toujours en train de pousser pour un partitionnement vertical de la table accrochée au méta-problème «trop large». Il a même renforcé son cas en obtenant une proposition d'un autre consultant de 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 ni exécuter d'analyse de performance.




Rien de bouleversant, je le reconnais, mais j'ai attrapé des gens en utilisant StringBuffer pour concaténer Strings 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();

C'était une pratique assez courante d'utiliser la technique en boucle, car elle était plus rapide. Le fait est que StringBuffer est synchronisé, donc il y a des frais supplémentaires si vous concattez seulement quelques Strings. (Sans compter que la différence est absolument triviale sur cette échelle.) Deux autres points sur cette pratique:

  1. StringBuilder n'est pas synchronisé, il doit donc être préféré à StringBuffer dans les cas où votre code ne peut pas être appelé à partir de plusieurs threads.
  2. Les compilateurs Java modernes transformeront la concaténation de chaînes lisible en bytecode optimisé pour vous quand cela est approprié de toute façon.



Qu'en est-il de POBI - pessimisation évidemment par intention?

Mon collègue dans les années 90 était fatigué de se faire botter la gueule par le PDG juste parce que le PDG a passé le premier jour de chaque version du logiciel ERP (une version personnalisée) à localiser les problèmes de performance dans les nouvelles fonctionnalités. Même si les nouvelles fonctionnalités géraient des gigaoctets et rendaient l'impossible possible, il trouvait toujours des détails, ou même des problèmes apparemment majeurs, sur lesquels se tourner. Il croyait en savoir beaucoup sur la programmation et a obtenu ses coups de pied en donnant un coup de pied aux culs programmeurs.

En raison de la nature incompétente de la critique (il était PDG, pas un informaticien), mon collègue n'a jamais réussi à faire les choses correctement. 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. Il a fallu 20 minutes seulement après sa mise en service, et il a reçu l'ordre de se présenter au bureau du PDG pour aller chercher ses insultes en retard.

Seule chose inhabituelle jusqu'ici était mes collègues muets quand il est revenu, souriant, plaisantant, sortant pour un BigMac ou deux alors qu'il normalement coup de pied les tables, flamme sur le PDG et l'entreprise, et passent le reste de la journée à la mort .

Naturellement, mon collègue s'est reposé pendant un ou deux jours à son bureau, améliorant ses compétences de visée dans Quake - puis le deuxième ou troisième jour il a supprimé les appels de retard, reconstruit et libéré un "patch d'urgence" dont il a propagé le mot qu'il avait passé 2 jours et 1 nuit à réparer les trous de performance.

Ce fut la première (et la seule) fois que le PDG maléfique a dit "excellent travail!" à lui. C'est tout ce qui compte, non?

C'était vrai POBI.

Mais c'est aussi une sorte d'optimisation des processus sociaux, donc c'est 100% correct.

Je pense.




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 vu.




Personne ne semble avoir mentionné le tri, alors je le ferai.

À plusieurs reprises, j'ai découvert que quelqu'un avait fabriqué un bubble-gate à la main, parce que la situation «n'exigeait pas» un appel à l'algorithme de «quicksort» «trop sophistiqué» qui existait déjà. Le développeur a été satisfait quand leur bubbleort fabriqué à la main a fonctionné assez bien sur les dix rangées de données qu'ils utilisent pour les tests. Il ne s'est pas passé aussi bien après que le client ait ajouté quelques milliers de lignes.




Le pire exemple que je puisse penser est une base de données interne dans mon entreprise contenant des informations sur tous les employés. Il reçoit une mise à jour tous les soirs des RH et dispose d'un service Web ASP.NET. De nombreuses autres applications utilisent le service Web pour peupler des éléments tels que les champs de recherche / de 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 de l'application lit toute la base de données et la convertit en objets en mémoire, stockés indéfiniment jusqu'à ce que le pool d'applications soit recyclé. Ce code était si lent, il faudrait 15 minutes pour charger moins de 2000 employés. Si vous avez accidentellement recyclé le pool d'applications pendant la journée, cela peut prendre 30 minutes ou plus, car chaque demande de service Web démarre plusieurs rechargements simultanés. Pour cette raison, les nouveaux employés n'apparaîtraient pas dans la base de données le premier jour de création de leur compte et ne pourraient donc pas accéder à la plupart des applications internes au cours de leurs deux premiers jours, en tournant leurs pouces.

Le deuxième niveau de pessimisme est que le directeur du développement ne veut pas le toucher de peur de briser des applications dépendantes, mais nous continuons à avoir des pannes sporadiques d'applications critiques à cause de la mauvaise conception d'un composant aussi simple.




Je devais une fois essayer de modifier le code qui incluait ces gemmes dans la classe des constantes

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 jeté le code avec plus de 200 utilisations dans 8 paquets différents.




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'un de ses trucs favoris était de changer une méthode lisible (en créant 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;
}

dans ceci:

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 lisible deviendrait " return " et toute autre logique serait remplacée par des expressions terniaires profondément imbriquées. Quand vous avez essayé de discuter de la façon dont cela était impossible à maintenir, il soulignait le fait que l'assemblage de sa méthode avait trois ou quatre instructions d'assemblage plus courtes. Ce n'était pas forcément plus rapide mais c'était toujours un peu plus court. C'était un système embarqué où l'utilisation de la mémoire importait occasionnellement, mais il y avait des optimisations beaucoup plus faciles qui auraient pu être faites que cela qui 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é à les changer en (*ptr).structElement sur la théorie qu'il était plus lisible et plus rapide aussi.

Transformer un code lisible en code illisible pour au plus 1% d'amélioration, et parfois même un code plus lent.




Je suppose que je pourrais offrir cette gemme:

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;
}

Puisque la racine carrée a été calculée à un endroit très sensible, j'ai eu la tâche de chercher un moyen de le 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é, 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 plus efficaces de 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. Creusant le contrôle de version, je peux aussi présenter la deuxième étape du refactoring, qui a même fait mieux que ce qui précède:

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, même s'il s'agit d'une implémentation légèrement différente, donc je suppose que cela se qualifie.




Toutes les contraintes de clé étrangère ont été supprimées d'une base de données, car sinon il y aurait tellement d'erreurs.




Vérification avant chaque opération javascript si l'objet sur lequel vous travaillez 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 de savoir s'il n'existe pas? Ne fais rien? Ne donnez pas la rétroaction à l'utilisateur?

Je suis d'accord que les erreurs Object expected l' Object expected sont ennuyeuses, mais ce n'est pas la meilleure solution pour cela.




Not exactly premature optimisation - but certainly misguided - this was read on the BBC website, from an article discussing Windows 7.

Mr Curran said that the Microsoft Windows team had been poring over every aspect of the operating system to make improvements. "We were able to shave 400 milliseconds off the shutdown time by slightly trimming the WAV file shutdown music.

Now, I haven't tried Windows 7 yet, so I might be wrong, but I'm willing to bet that there are other issues in there that are more important than how long it takes to shut-down. After all, once I see the 'Shutting down Windows' message, the monitor is turned off and I'm walking away - how does that 400 milliseconds benefit me?




An ex-coworker of mine (a s.o.a.b. , actually) was assigned to build a new module for our Java ERP that should have collected and analyzed customers' data (retail industry). He decided to split EVERY Calendar/Datetime field in its components (seconds, minutes, hours, day, month, year, day of week, bimester, trimester (!)) because "how else would I query for 'every monday'?"




Related