L'orientation objet affecte-t-elle vraiment les performances de l'algorithme?


14

L'orientation objet m'a beaucoup aidé dans l'implémentation de nombreux algorithmes. Cependant, les langages orientés objet vous guident parfois dans une approche "simple" et je doute que cette approche soit toujours une bonne chose.

OO est vraiment utile pour coder les algorithmes rapidement et facilement. Mais cette POO pourrait-elle être un inconvénient pour les logiciels basés sur les performances, c'est-à-dire à quelle vitesse le programme s'exécute-t-il?

Par exemple, le stockage de nœuds de graphe dans une structure de données semble "simple" en premier lieu, mais si les objets Node contiennent de nombreux attributs et méthodes, cela pourrait-il conduire à un algorithme lent?

En d'autres termes, de nombreuses références entre de nombreux objets différents, ou en utilisant de nombreuses méthodes de nombreuses classes, pourraient-elles entraîner une implémentation "lourde"?


1
Une question assez étrange. Je peux comprendre comment la POO aide à un niveau d'architecture. Mais un niveau d'implémentation d'algorithmes est normalement construit sur des abstractions qui sont très étrangères à tout ce que la POO représente. Donc, il y a de fortes chances que les performances ne soient pas le plus gros problème pour vos implémentations d'algorithmes OOP. En ce qui concerne les performances, avec la POO, le plus gros goulot d'étranglement est normalement lié aux appels virtuels.
SK-logic

@ SK-logic> l'orientation des objets a tendance à être manipulée par le pointeur, ce qui implique une charge de travail plus importante du côté de l'allocation de mémoire, et les données non localisées ont tendance à ne pas être dans le cache du processeur et, enfin et surtout, impliquent beaucoup d'indirect branchement (fonctions virtuelles) qui est mortel pour le pipeline CPU. OO est une bonne chose, mais il peut certainement avoir un coût de performance dans certains cas.
deadalnix

Si les nœuds de votre graphique ont une centaine d'attributs, vous aurez besoin de place pour les stocker quel que soit le paradigme utilisé pour la mise en œuvre réelle, et je ne vois pas comment un seul paradigme a un avantage en général. @deadalnix: Peut-être que les facteurs constants peuvent être pires en raison du durcissement de certaines optimisations. Mais notez que je dis plus fort , pas impossible - par exemple, PyPy peut déballer des objets dans des boucles serrées et les JVM insèrent les appels de fonctions virtuelles depuis toujours.

Python est bon pour les algorithmes de prototypage, et pourtant vous n'avez souvent pas besoin d'une classe lors de l'implémentation d'un algorithme typique.
Job

1
+1 Pour relier l'orientation des objets aux algorithmes, quelque chose qui est négligé de nos jours, à la fois dans l'industrie du logiciel et dans les
universités

Réponses:


16

L'orientation des objets peut empêcher certaines optimisations algorithmiques, en raison de l'encapsulation. Deux algorithmes peuvent fonctionner particulièrement bien ensemble, mais s'ils sont cachés derrière des interfaces OO, la possibilité d'utiliser leur synergie est perdue.

Regardez les bibliothèques numériques. Beaucoup d'entre eux (pas seulement ceux écrits dans les années 60 ou 70) ne sont pas des POO. Il y a une raison à cela - les algorithmes numériques fonctionnent mieux comme un ensemble de découplages modulesque comme des hiérarchies OO avec interfaces et encapsulation.


2
La raison principale en est que seul C ++ a découvert qu'il fallait utiliser des modèles d'expression pour rendre la version OO aussi efficace.
DeadMG

4
Regardez les bibliothèques C ++ modernes (STL, Boost) - elles ne sont pas du tout OOP aussi. Et pas seulement à cause des performances. Les algorithmes ne peuvent normalement pas être bien représentés dans un style POO. Des choses comme la programmation générique sont beaucoup mieux adaptées aux algorithmes de bas niveau.
SK-logic

3
Wha-wha-quoi? Je suppose que je viens d'une planète différente de quant_dev et SK-logic. Non, un univers différent. Avec différentes lois de la physique et tout.
Mike Nakis

5
@MikeNakis: la différence de point de vue réside dans (1) si un certain morceau de code informatique peut bénéficier en termes de lisibilité humaine de la POO (ce que les recettes numériques ne sont pas); (2) si la conception de la classe OOP s'aligne sur la structure de données et l'algorithme optimaux (voir ma réponse); et (3) si chaque couche d'indirection fournit une "valeur" suffisante (en termes de travail effectué par appel de fonction ou de clarté conceptuelle par couche) justifie la surcharge (due à l'indirection, l'appel de fonction, les couches ou la copie de données). (4) Enfin, la sophistication du compilateur / JIT / optimiseur est un facteur limitant.
rwong

2
@MikeNakis, que voulez-vous dire? Pensez-vous que STL est une bibliothèque OOP? La programmation générique ne fonctionne pas bien avec la POO de toute façon. Et inutile de mentionner que la POO est un cadre trop étroit, adapté uniquement à quelques tâches pratiques, étranger à toute autre chose.
SK-logic

9

Qu'est-ce qui détermine les performances?

Les fondamentaux: structures de données, algorithmes, architecture informatique, matériel. Plus les frais généraux.

Un programme POO peut être conçu pour s'aligner exactement avec le choix des structures de données et des algorithmes qui est jugé optimal par la théorie CS. Il aura les mêmes caractéristiques de performance que le programme optimal, plus quelques frais généraux. Les frais généraux peuvent généralement être minimisés.

Cependant, un programme qui est initialement conçu avec uniquement des préoccupations de POO, sans concerner les fondamentaux, peut être initialement sous-optimal. La sous-optimalité est parfois supprimable par refactoring; parfois ce n'est pas le cas - nécessitant une réécriture complète.

Avertissement: la performance est-elle importante dans les logiciels d'entreprise?

Oui, mais le time-to-market (TTM) est plus important, par ordre de grandeur. Les logiciels d'entreprise mettent l'accent sur l'adaptabilité du code à des règles commerciales complexes. Les mesures des performances doivent être prises tout au long du cycle de développement. (Voir la section: que signifient les performances optimales? ) Seules des améliorations commercialisables doivent être apportées et introduites progressivement dans les versions ultérieures.

Que signifie une performance optimale?

En général, le problème avec les performances du logiciel est que: pour prouver qu '"une version plus rapide existe", cette version plus rapide doit d'abord exister (c'est-à-dire aucune preuve autre qu'elle-même).

Parfois, cette version plus rapide est d'abord vue dans une langue ou un paradigme différent. Cela devrait être considéré comme un indice d'amélioration, et non comme un jugement d'infériorité de certaines autres langues ou paradigmes.

Pourquoi faisons-nous de la POO si cela peut entraver notre recherche de performances optimales?

La POO introduit des frais généraux (dans l'espace et l'exécution), en échange de l'amélioration de la "maniabilité" et donc de la valeur commerciale du code. Cela réduit le coût du développement et de l'optimisation. Voir @MikeNakis .

Quelles parties de la POO peuvent encourager une conception initialement sous-optimale?

Les parties de la POO qui (i) encouragent la simplicité / l'intuitivité, (ii) l'utilisation de méthodes de conception familières au lieu de principes fondamentaux, (iii) découragent de multiples implémentations personnalisées du même objectif.

  • BAISER
  • YAGNI
  • SEC
  • Conception d'objet (par exemple avec des cartes CRC) sans penser aux principes de base)

Une application stricte de certaines directives de POO (encapsulation, passage de message, bien faire une chose) entraînera en effet un ralentissement du code au début. Les mesures de performances aideront à diagnostiquer ces problèmes. Tant que la structure des données et l'algorithme s'alignent sur la conception optimale prévue par la théorie, les frais généraux peuvent généralement être minimisés.

Quelles sont les atténuations courantes des frais généraux de POO?

Comme indiqué précédemment, en utilisant des structures de données optimales pour la conception.

Certains langages prennent en charge l'incrustation de code qui peut récupérer des performances d'exécution.

Comment pourrions-nous adopter la POO sans sacrifier les performances?

Apprenez et appliquez à la fois la POO et les principes fondamentaux.

Il est vrai que le strict respect de la POO peut vous empêcher d'écrire une version plus rapide. Parfois, une version plus rapide ne peut être écrite qu'à partir de zéro. C'est pourquoi il est utile d'écrire plusieurs versions de code en utilisant différents algorithmes et paradigmes (POO, générique, fonctionnel, mathématique, spaghetti), puis d'utiliser des outils d'optimisation pour que chaque version approche les performances maximales observées.

Existe-t-il des types de code qui ne bénéficieront pas de la POO?

(Extrait de la discussion entre [@quant_dev], [@ SK-logic] et [@MikeNakis])

  1. Recettes numériques, qui proviennent des mathématiques.
    • Les équations mathématiques et les transformations elles-mêmes peuvent être comprises comme des objets.
    • Des techniques de transformation de code très sophistiquées sont nécessaires pour générer un code exécutable efficace. L'implémentation naïve ("tableau blanc") aura des performances abyssales.
    • Cependant, les compilateurs traditionnels d'aujourd'hui ne sont pas en mesure de le faire.
    • Les logiciels spécialisés (MATLAB et Mathematica, etc.) ont des solveurs JIT et symboliques capables de générer du code efficace pour certains sous-problèmes. Ces solveurs spécialisés peuvent être considérés comme des compilateurs à usage spécial (médiateurs entre du code lisible par l'homme et du code exécutable par une machine) qui bénéficieront eux-mêmes d'une conception POO.
    • Chaque sous-problème nécessite son propre "compilateur" et ses "transformations de code". Il s'agit donc d'un domaine de recherche ouvert très actif avec de nouveaux résultats qui apparaissent chaque année.
    • Parce que la recherche prend beaucoup de temps, les rédacteurs de logiciels doivent effectuer une optimisation sur papier et transcrire le code optimisé en logiciel. Le code transcrit pourrait en effet être inintelligible.
  2. Code de très bas niveau.
      *

8

Il ne s'agit pas vraiment de l'orientation des objets mais des conteneurs. Si vous avez utilisé une double liste chaînée pour stocker des pixels dans votre lecteur vidéo, cela va en souffrir.

Cependant, si vous utilisez le bon conteneur, il n'y a aucune raison qu'un std :: vector soit plus lent qu'un tableau, et puisque vous avez déjà tous les algorithmes courants écrits pour lui - par des experts - c'est probablement plus rapide que votre code de tableau laminé à la maison.


1
Parce que les compilateurs sont sous-optimaux (ou que les règles du langage de programmation interdisent de tirer parti de certaines hypothèses ou optimisations), il existe en effet un surcoût qui ne peut pas être supprimé. De plus, certaines optimisations, par exemple la vectorisation, ont des exigences d'organisation des données (par exemple, la structure des tableaux au lieu du tableau des structures) que la POO peut soit améliorer, soit entraver. (J'ai récemment travaillé sur une tâche d'optimisation std :: vector.)
rwong

5

La POO est évidemment une bonne idée et, comme toute bonne idée, elle peut être surutilisée. D'après mon expérience, c'est beaucoup trop utilisé. Mauvaises performances et mauvais résultat de maintenabilité.

Cela n'a rien à voir avec le surcoût d'appeler des fonctions virtuelles, et peu à voir avec ce que fait l'optimiseur / la gigue.

Cela a tout à voir avec les structures de données qui, tout en ayant les meilleures performances big-O, ont de très mauvais facteurs constants. Cela se fait en supposant que s'il y a un problème de limitation des performances dans l'application, c'est ailleurs.

L'une des façons dont cela se manifeste est le nombre de fois par seconde que de nouvelles exécutions sont supposées avoir des performances O (1), mais peuvent exécuter des centaines à des milliers d'instructions (y compris la suppression correspondante ou l'heure GC). Cela peut être atténué en enregistrant les objets utilisés, mais cela rend le code moins "propre".

Il se manifeste également par la façon dont les gens sont encouragés à écrire des fonctions de propriété, des gestionnaires de notifications, des appels à des fonctions de classe de base, toutes sortes d'appels de fonctions souterraines qui existent pour essayer de maintenir la cohérence. Pour maintenir la cohérence, ils ont un succès limité, mais ils réussissent énormément aux cycles de perte. Les programmeurs comprennent le concept de données normalisées mais ils ont tendance à l'appliquer uniquement à la conception de bases de données. Ils ne l'appliquent pas à la conception de la structure de données, du moins en partie parce que la POO leur dit qu'ils n'ont pas à le faire. Une chose aussi simple que de définir un bit modifié dans un objet peut entraîner un tsunami de mises à jour dans la structure de données, car aucune classe digne de ce code ne prend un appel modifié et ne le stocke .

Peut-être que les performances d'une application donnée sont très bien telles qu'écrites.

D'un autre côté, s'il y a un problème de performances, voici un exemple de la façon dont je le règle. C'est un processus en plusieurs étapes. À chaque étape, une activité particulière représente une grande partie du temps et pourrait être remplacée par quelque chose de plus rapide. (Je n'ai pas dit "goulot d'étranglement". Ce ne sont pas les types de choses que les profileurs sont bons à trouver.) Ce processus nécessite souvent, afin d'obtenir l'accélération, le remplacement en gros de la structure des données. Souvent, cette structure de données est là uniquement parce que c'est une pratique recommandée de POO.


3

En théorie, cela pourrait conduire à une lenteur, mais même alors, ce ne serait pas un algorithme lent, ce serait une implémentation lente. Dans la pratique, l'orientation objet vous permettra d'essayer différents scénarios de simulation (ou de revoir l'algorithme à l'avenir) et ainsi de lui apporter des améliorations algorithmiques , que vous ne pourriez jamais espérer obtenir si vous l'aviez écrit de la manière spaghetti dans le premier lieu, car la tâche serait intimidante. (Il faudrait essentiellement réécrire le tout.)

Par exemple, en ayant divisé les différentes tâches et entités en objets épurés, vous pourrez facilement entrer plus tard et, par exemple, intégrer une fonction de mise en cache entre certains objets (transparente pour eux), ce qui pourrait générer des milliers. amélioration de pli.

Généralement, les types d'améliorations que vous pouvez réaliser en utilisant un langage de bas niveau (ou des astuces astucieuses avec un langage de haut niveau) donnent des améliorations de temps constantes (linéaires), qui ne figurent pas en termes de notation big-oh. Avec des améliorations algorithmiques, vous pourrez peut-être réaliser des améliorations non linéaires. C'est inestimable.


1
+1: la différence entre les spaghettis et le code orienté objet (ou le code écrit dans un paradigme bien défini) est: chaque version d'un bon code réécrit apporte une nouvelle compréhension du problème. Chaque version de spaghetti réécrite n'apporte aucun aperçu.
rwong

@rwong ne pourrait pas être mieux expliqué ;-)
umlcat

3

Mais cette POO pourrait-elle être un inconvénient pour les logiciels basés sur les performances, c'est-à-dire à quelle vitesse le programme s'exécute-t-il?

Souvent oui !!! MAIS...

En d'autres termes, de nombreuses références entre de nombreux objets différents, ou en utilisant de nombreuses méthodes de nombreuses classes, pourraient-elles entraîner une implémentation "lourde"?

Pas nécessairement. Cela dépend de la langue / du compilateur. Par exemple, un compilateur C ++ optimisant, à condition que vous n'utilisiez pas de fonctions virtuelles, réduira souvent la surcharge de votre objet à zéro. Vous pouvez faire des choses comme écrire un wrapper sur unint là ou un pointeur intelligent de portée sur un ancien pointeur ordinaire qui fonctionne aussi rapidement que l'utilisation directe de ces anciens types de données simples.

Dans d'autres langages comme Java, il y a un peu de surcharge pour un objet (souvent assez petit dans de nombreux cas, mais astronomique dans de rares cas avec des objets vraiment minuscules). Par exemple, Integeril est considérablement moins efficace que int(prend 16 octets contre 4 sur 64 bits). Pourtant, ce n'est pas seulement un gaspillage flagrant ou quoi que ce soit de ce genre. En échange, Java offre des éléments tels que la réflexion sur chaque type défini par l'utilisateur de manière uniforme, ainsi que la possibilité de remplacer toute fonction non marquée comme final.

Prenons cependant le meilleur des cas: le compilateur C ++ optimisant qui peut optimiser les interfaces d'objet jusqu'à zéro surcharge. Même alors, la POO dégrade souvent les performances et l'empêche d'atteindre le pic. Cela pourrait ressembler à un paradoxe complet: comment pourrait-il en être ainsi? Le problème réside dans:

Conception d'interface et encapsulation

Le problème est que même lorsqu'un compilateur peut écraser la structure d'un objet jusqu'à zéro surcharge (ce qui est au moins très souvent vrai pour l'optimisation des compilateurs C ++), l'encapsulation et la conception d'interface (et les dépendances accumulées) des objets à grain fin empêcheront souvent la représentations de données les plus optimales pour les objets qui sont destinés à être agrégés par les masses (ce qui est souvent le cas pour les logiciels à performances critiques).

Prenez cet exemple:

class Particle
{
public:
    ...

private:
    double birth;                // 8 bytes
    float x;                     // 4 bytes
    float y;                     // 4 bytes
    float z;                     // 4 bytes
    /*padding*/                  // 4 bytes of padding
};
Particle particles[1000000];     // 1mil particles (~24 megs)

Supposons que notre modèle d'accès à la mémoire consiste simplement à parcourir ces particules séquentiellement et à les déplacer autour de chaque image à plusieurs reprises, en les faisant rebondir dans les coins de l'écran, puis en rendant le résultat.

Nous pouvons déjà voir un surdébit de remplissage de 4 octets flagrant requis pour aligner birthcorrectement le membre lorsque les particules sont agrégées de manière contiguë. Déjà ~ 16,7% de la mémoire est gaspillée avec l'espace mort utilisé pour l'alignement.

Cela peut sembler théorique, car nous avons actuellement des gigaoctets de DRAM. Pourtant, même les machines les plus bestiales que nous avons aujourd'hui n'ont souvent que 8 mégaoctets quand il s'agit de la région la plus lente et la plus grande du cache CPU (L3). Moins nous pouvons nous y adapter, plus nous le payons en termes d'accès répété aux DRAM, et plus les choses ralentissent. Du coup, le gaspillage de 16,7% de mémoire ne semble plus être une affaire banale.

Nous pouvons facilement éliminer cette surcharge sans aucun impact sur l'alignement du champ:

class Particle
{
public:
    ...

private:
    float x;                     // 4 bytes
    float y;                     // 4 bytes
    float z;                     // 4 bytes
};
Particle particles[1000000];     // 1mil particles (~12 megs)
double particle_birth[1000000];  // 1mil particle births (~8 bytes)

Maintenant, nous avons réduit la mémoire de 24 Mo à 20 Mo. Avec un modèle d'accès séquentiel, la machine consommera désormais ces données un peu plus rapidement.

Mais regardons ce birthdomaine de plus près. Disons qu'il enregistre l'heure de début de la naissance (création) d'une particule. Imaginez que le champ n'est accessible que lorsqu'une particule est créée pour la première fois, et toutes les 10 secondes pour voir si une particule doit mourir et renaître à un endroit aléatoire sur l'écran. Dans ce cas, birthest un champ froid. Il n'est pas accessible dans nos boucles critiques pour les performances.

Par conséquent, les données critiques pour les performances ne sont pas de 20 mégaoctets mais en fait un bloc contigu de 12 mégaoctets. La mémoire chaude réelle à laquelle nous accédons fréquemment a diminué de moitié . Attendez - vous à des accélérations importantes par rapport à notre solution originale de 24 mégaoctets (n'a pas besoin d'être mesurée - déjà fait ce genre de choses mille fois, mais n'hésitez pas en cas de doute).

Pourtant, remarquez ce que nous avons fait ici. Nous avons complètement rompu l'encapsulation de cet objet de particules. Son état est désormais divisé entre Particleles champs privés d' un type et un tableau parallèle séparé. Et c'est là qu'intervient la conception granulaire orientée objet.

Nous ne pouvons pas exprimer la représentation optimale des données lorsqu'il est confiné à la conception d'interface d'un seul objet très granulaire comme une seule particule, un seul pixel, même un seul vecteur à 4 composants, peut-être même un seul objet "créature" dans un jeu , etc. La vitesse d'un guépard sera gaspillée s'il se trouve sur une île minuscule de 2 mètres carrés, et c'est ce que la conception orientée objet très granulaire fait souvent en termes de performances. Il limite la représentation des données à une nature sous-optimale.

Pour aller plus loin, disons que puisque nous ne faisons que déplacer des particules, nous pouvons en fait accéder à leurs champs x / y / z dans trois boucles distinctes. Dans ce cas, nous pouvons bénéficier des intrinsèques SIMD de style SoA avec des registres AVX qui peuvent vectoriser 8 opérations SPFP en parallèle. Mais pour ce faire, nous devons maintenant utiliser cette représentation:

float particle_x[1000000];       // 1mil particle X positions (~4 megs)
float particle_y[1000000];       // 1mil particle Y positions (~4 megs)
float particle_z[1000000];       // 1mil particle Z positions (~4 megs)
double particle_birth[1000000];  // 1mil particle births (~8 bytes)

Nous volons maintenant avec la simulation de particules, mais regardez ce qui est arrivé à notre conception de particules. Il a été complètement démoli et nous examinons maintenant 4 tableaux parallèles et aucun objet pour les agréger. Notre Particleconception orientée objet est devenue sayonara.

Cela m'est arrivé plusieurs fois en travaillant dans des domaines critiques pour les performances, où les utilisateurs exigent de la vitesse, seule l'exactitude étant la seule chose qu'ils demandent le plus. Ces petites conceptions orientées objet minuscules ont dû être démolies, et les ruptures en cascade ont souvent nécessité que nous utilisions une stratégie de dépréciation lente vers la conception plus rapide.

Solution

Le scénario ci-dessus ne présente qu'un problème avec les conceptions granulaires orientées objet. Dans ces cas, nous finissons souvent par devoir démolir la structure afin d'exprimer des représentations plus efficaces en raison des répétitions SoA, de la division de champ chaud / froid, de la réduction de remplissage pour les modèles d'accès séquentiel (le remplissage est parfois utile pour les performances avec accès aléatoire dans les cas d'AoS, mais presque toujours un obstacle pour les modèles d'accès séquentiel), etc.

Pourtant, nous pouvons prendre cette représentation finale sur laquelle nous nous sommes installés et modéliser une interface orientée objet:

// Represents a collection of particles.
class ParticleSystem
{
public:
    ...

private:
    double particle_birth[1000000];  // 1mil particle births (~8 bytes)
    float particle_x[1000000];       // 1mil particle X positions (~4 megs)
    float particle_y[1000000];       // 1mil particle Y positions (~4 megs)
    float particle_z[1000000];       // 1mil particle Z positions (~4 megs)
};

Maintenant, nous allons bien. Nous pouvons obtenir tous les goodies orientés objet que nous aimons. Le guépard a tout un pays à parcourir aussi vite qu'il le peut. Nos conceptions d'interface ne nous piègent plus dans un goulot d'étranglement.

ParticleSystempeut même être abstrait et utiliser des fonctions virtuelles. C'est sans objet maintenant, nous payons les frais généraux au niveau de la collection de particules plutôt qu'au niveau par particule . Les frais généraux représentent 1/1 000 000 de ce qu'il en serait autrement si nous modélisions des objets au niveau des particules individuelles.

C'est donc la solution dans de véritables domaines critiques pour les performances qui gèrent une charge élevée, et pour toutes sortes de langages de programmation (cette technique profite au C, C ++, Python, Java, JavaScript, Lua, Swift, etc.). Et il ne peut pas facilement être qualifié d '"optimisation prématurée", car cela concerne la conception et l' architecture de l' interface . Nous ne pouvons pas écrire une base de code modélisant une seule particule comme un objet avec une cargaison de dépendances client dans unParticle'sinterface publique, puis changer d'avis plus tard. J'ai fait beaucoup de choses lors de mon appel pour optimiser les bases de code héritées, et cela peut prendre des mois à réécrire soigneusement des dizaines de milliers de lignes de code pour utiliser la conception plus volumineuse. Cela affecte idéalement la façon dont nous concevons les choses à l'avance à condition de pouvoir anticiper une charge élevée.

Je continue de faire écho à cette réponse sous une forme ou une autre dans de nombreuses questions de performance, et en particulier celles qui concernent la conception orientée objet. La conception orientée objet peut toujours être compatible avec les besoins de performances les plus exigeants, mais nous devons changer un peu la façon dont nous y pensons. Nous devons donner à ce guépard un espace pour courir aussi vite que possible, et c'est souvent impossible si nous concevons de petits objets minuscules qui stockent à peine n'importe quel état.


Fantastique. C'est ce que je cherchais réellement en termes de combinaison de POO avec une demande de haute performance. Je ne comprends vraiment pas pourquoi ce n'est pas plus voté.
pbx

2

Oui, la mentalité orientée objet peut certainement être neutre ou négative en ce qui concerne la programmation haute performance, à la fois au niveau algorithmique et de la mise en œuvre. Si la POO remplace l'analyse algorithmique, cela peut vous conduire à une implémentation prématurée et, au niveau le plus bas, les abstractions de POO doivent être mises de côté.

Le problème découle de l'accent mis par la POO sur la réflexion sur les instances individuelles. Je pense qu'il est juste de dire que la façon OOP de penser à un algorithme est de penser à un ensemble spécifique de valeurs et de le mettre en œuvre de cette façon. Si c'est votre chemin le plus élevé, il est peu probable que vous réalisiez une transformation ou une restructuration qui conduirait à des gains de Big O.

Au niveau algorithmique, il pense souvent à une vue d'ensemble et aux contraintes ou relations entre les valeurs qui conduisent à des gains de Big O. Un exemple pourrait être qu'il n'y a rien dans l'état d'esprit de POO qui vous amènerait à transformer "la somme d'une plage continue d'entiers" d'une boucle en(max + min) * n/2

Au niveau de l'implémentation, bien que les ordinateurs soient "assez rapides" pour la plupart des algorithmes au niveau de l'application, dans le code critique de performance de bas niveau, on s'inquiète beaucoup de la localité. Encore une fois, l'accent mis sur la POO sur une instance individuelle et les valeurs d'un passage dans la boucle peut être négatif. Dans un code très performant, au lieu d'écrire une boucle simple, vous souhaiterez peut-être dérouler partiellement la boucle, regrouper plusieurs instructions de chargement en haut, puis les transformer en groupe, puis les écrire dans un groupe. Pendant ce temps, vous feriez attention aux calculs intermédiaires et, énormément, au cache et à l'accès à la mémoire; problèmes où les abstractions POO ne sont plus valides. Et, si elles sont suivies, elles peuvent être trompeuses: à ce niveau, vous devez connaître et réfléchir aux représentations au niveau de la machine.

Lorsque vous regardez quelque chose comme les primitives de performance d'Intel, vous avez littéralement des milliers d'implémentations de la transformation de Fourier rapide, chacune ajustée pour mieux fonctionner pour une taille de données et une architecture de machine spécifiques. (Il est fascinant de constater que la majeure partie de ces implémentations sont générées par des machines: Markus Püschel Automatic Performance Programming )

Bien sûr, comme la plupart des réponses l'ont dit, pour la plupart des développements, pour la plupart des algorithmes, la POO n'est pas pertinente pour les performances. Tant que vous n'êtes pas «pessimisant prématurément» et que vous ajoutez beaucoup d'appels non locaux, le thispointeur n'est ni ici ni là.


0

C'est lié et souvent négligé.

Ce n'est pas une réponse facile, cela dépend de ce que vous voulez faire.

Certains algorithmes ont de meilleures performances en utilisant une programmation structurée simple, tandis que d'autres utilisent mieux l'orientation des objets.

Avant Orientation Objet, de nombreuses écoles enseignent la conception d'algorithmes (ed) avec une programmation structurée. Aujourd'hui, de nombreuses écoles enseignent la programmation orientée objet, ignorant la conception et les performances des algorithmes.

Bien sûr, il y avait des écoles qui enseignent la programmation structurée, qui ne se souciaient pas du tout des algorithmes.


0

Les performances se résument finalement aux cycles CPU et mémoire. Mais la différence en pourcentage entre la surcharge de messagerie et d'encapsulation OOP et une sémantique de programmation ouverte plus large peut ou non être un pourcentage suffisamment important pour faire une différence notable dans les performances de votre application. Si une application est liée au disque ou au cache de données, toute surcharge de POO peut être complètement perdue dans le bruit.

Mais, dans les boucles internes du traitement du signal et de l'image en temps réel et d'autres applications liées au calcul numérique, la différence peut bien être un pourcentage significatif de cycles CPU et mémoire, ce qui peut rendre toute surcharge OOP beaucoup plus coûteuse à exécuter.

La sémantique d'un langage OOP particulier peut ou non exposer suffisamment d'opportunités pour que le compilateur optimise ces cycles, ou pour que les circuits de prédiction de branche du CPU devinent toujours correctement et couvrent ces cycles avec la prélecture et le pipelining.


0

Une bonne conception orientée objet m'a aidé à accélérer considérablement une application. Il fallait générer des graphiques complexes de manière algorithmique. Je l'ai fait via l'automatisation de Microsoft Visio. J'ai travaillé, mais j'ai été incroyablement lent. Heureusement, j'avais inséré un niveau d'abstraction supplémentaire entre la logique (l'algorithme) et la substance Visio. Mon composant Visio a exposé ses fonctionnalités via une interface. Cela m'a permis de remplacer facilement le composant lent par un autre créant des fichiers SVG, qui était au moins 50 fois plus rapide! Sans une approche propre et orientée objet, les codes de l'algorithme et du contrôle Vision auraient été enchevêtrés d'une manière qui aurait transformé le changement en cauchemar.


vouliez-vous dire OO Design appliqué avec un langage procédural, ou OO Design & OO langage de programmation?
umlcat

Je parle d'une application C #. La conception et le langage sont tous deux OO Alors que OO-iness du langage introduira quelques petits succès de performance (appels de méthode virtuelle, création d'objet, accès membre via l'interface), la conception OO m'a aidé à créer une application beaucoup plus rapide. Ce que je veux dire, c'est: Oubliez les résultats de performance en raison de OO (langage et design). À moins que vous ne fassiez des calculs lourds avec des millions d'itérations, OO ne vous nuira pas. Là où vous perdez généralement beaucoup de temps, c'est les E / S.
Olivier Jacot-Descombes
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.