11 votes

Y a-t-il un avantage à utiliser l'ancien style `objet` au lieu de `classe` dans Delphi ?

Dans Delphi sain d'esprit les gens utilisent un class pour définir des objets.
Dans Turbo Pascal pour Windows, nous utilisions object et aujourd'hui, vous pouvez toujours utiliser object pour créer un objet.

La différence est qu'un object vit sur la pile et un class vit sur le tas.
Et bien sûr, le object est déprécié.

Putting all that aside:

Y a-t-il un avantage à tirer, en termes de rapidité, en utilisant object au lieu de la classe ?

Je sais que object est cassé dans Delphi 2009, mais j'ai un cas d'utilisation particulier. 1) où la vitesse est importante et j'essaie de savoir si l'utilisation de object rendra mon truc plus rapide sans le rendre bogué.
Cette base de code est en Delphi 7, mais je pourrais la porter en Delphi 2007, je n'ai pas encore décidé.


1) Le jeu de la vie de Conway

Long commentaire
Merci à tous de m'avoir aiguillé dans la bonne direction.

Laissez-moi vous expliquer un peu plus. J'essaie de faire une implémentation plus rapide de hashlife , voir aussi ici o ici pour un code source simple

Le détenteur actuel du record est golly mais golly utilise une traduction directe du code lisp original de Bill Gospher (qui est brillant comme algorithme, mais pas du tout optimisé au niveau micro). Hashlife permet de calculer une génération en temps O(log(n)).

Pour ce faire, il utilise un compromis espace/temps. Pour cette raison, hashlife a besoin de beaucoup de mémoire, des gigaoctets ne sont pas rares. En retour, vous pouvez calculer la génération 2^128 (340282366920938463463374607431770000000) en utilisant la génération 2^127 (170141183460469231731687303715880000000) en o(1) temps.

Comme hashlife doit calculer des hachages pour tous les sous-modèles qui apparaissent dans un modèle plus grand, l'allocation des objets doit être rapide.

Voici la solution que j'ai trouvée :

Optimisation de l'allocation
J'alloue un grand bloc de mémoire physique (réglable par l'utilisateur), disons 512 Mo. A l'intérieur de ce bloc, j'alloue ce que j'appelle piles de fromage . Il s'agit d'une pile normale où je pousse et saute, mais un saute peut aussi provenir du milieu de la pile. Si cela se produit, je le marque sur le free (il s'agit d'une pile normale). En poussant, je vérifie le free Si rien n'est libre, je pousse comme d'habitude. Je vais utiliser les enregistrements comme conseillé, cela semble être la solution avec le moins de frais généraux.

En raison de la façon dont le hashlife fonctionne, très peu pop ping a lieu et beaucoup de push es. Je garde des piles séparées pour les structures de différentes tailles, en m'assurant de garder l'accès à la mémoire aligné sur les frontières 4/8/16 octets.

Autres optimisations

  • suppression de la récursion
  • optimisation de la mémoire cache
  • l'utilisation de inline
  • précalcul des hachages (à la manière des tables arc-en-ciel)
  • détection des cas pathologiques et utilisation de l'algorithme de repli
  • utilisation du GPU

15voto

Arnaud Bouchez Points 25855

Pour utiliser la programmation OOP normale, il faut doit toujours utiliser le class genre . Vous disposerez du modèle d'objet le plus puissant de Delphi, y compris les éléments suivants

1. Enregistrements, pointeurs et objets

Les dossiers peuvent être mauvais (copie cachée lente si vous avez oublié de déclarer un paramètre en tant que const , enregistrer le code caché de nettoyage lent, un fillchar ferait que n'importe quelle chaîne en enregistrement deviendrait une fuite de mémoire...), mais ils sont parfois très pratiques pour accéder à une structure binaire (par exemple une "petite valeur"), via un pointeur.

Un tableau dynamique de petits enregistrements (par exemple, avec un champ entier et un champ double) sera beaucoup plus rapide qu'un TList de petites classes ; avec notre TDynArray emballage vous aurez un accès de haut niveau aux enregistrements, avec sérialisation, tri, hachage et autres.

Si vous utilisez des pointeurs, vous devez savoir ce que vous faites. Il est définitivement plus préférable de s'en tenir aux classes, et TPersistent si vous voulez utiliser le magique "modèle de propriété des composants VCL".

L'héritage n'est pas autorisé pour les enregistrements. Vous devrez soit utiliser une "variante d'enregistrement" (en utilisant l'attribut case dans sa définition de type), soit utiliser des enregistrements imbriqués. Lorsque vous utilisez une API de type C, vous devrez parfois utiliser des structures orientées objet. L'utilisation d'enregistrements imbriqués ou de variantes d'enregistrements est, à mon avis, beaucoup moins claire que le bon vieux modèle d'héritage "objet".

2. Quand utiliser l'objet

Mais il y a des endroits où les objets sont un bon moyen d'accéder à des données déjà existantes.

Même le modèle objet est meilleur que le nouveau modèle d'enregistrement, car il gère l'héritage simple.

Sur un article du Blog l'été dernier j'ai affiché quelques possibilités d'utiliser encore des objets :

  • Un fichier mappé en mémoire, que je veux analyser très rapidement : un pointeur vers un tel objet est tout simplement génial, et vous avez toujours des méthodes à portée de main ; je l'utilise pour TFileHeader ou TFileInfo qui mappe l'en-tête .zip, dans SynZip.pas ;

  • Une structure Win32, définie par un appel API, dans laquelle je place des méthodes pratiques pour un accès facile aux données (pour cela vous pouvez utiliser record mais s'il y a une orientation objet dans la structure - ce qui est très courant - vous devrez imbriquer les records, ce qui n'est pas très pratique) ;

  • Une structure temporaire définie sur la pile, juste utilisée pendant une procédure : Je l'utilise pour TZStream dans SynZip.pas, ou pour nos classes RTTI, qui mappent le RTTI généré par Delphi d'une manière orientée objet et non comme TypeInfo qui est orienté fonction/procédure. En mappant directement le contenu de la mémoire RTTI, notre code est plus rapide qu'en utilisant les nouvelles classes RTTI créées sur le tas. Nous n'instancions pas de mémoire, ce qui, pour un framework ORM comme le nôtre, est bon pour sa vitesse. Nous avons besoin de beaucoup d'informations RTTI, mais nous en avons besoin rapidement, nous en avons besoin directement.

3. Comment l'implémentation des objets est cassée dans le Delphi moderne

Le fait que l'objet soit cassé dans le Delphi moderne est une honte, IMHO.

Normalement, si vous définissez un enregistrement sur la pile, contenant des variables comptées par référence (comme une chaîne), il sera initialisé par un code magique du compilateur, au niveau du début de la méthode/fonction :

type TObj = object Int: integer; Str: string; end;
procedure Test;
var O: TObj
begin // here, an _InitializeRecord(@O,TypeInfo(TObj)) call is made
  O.Str := 'test';
  (...)
end;  // here, a _FinalizeRecord(@O,TypeInfo(TObj)) call is made

Ces _InitializeRecord y _FinalizeRecord va "préparer" puis "libérer" la variable O.Str.

Avec Delphi 2010, je me suis aperçu que parfois, cette _InitializeRecord() n'était pas toujours faite. Si l'enregistrement n'a que quelques champs non publics, les appels cachés ne sont parfois pas générés par le compilateur.

Il suffit de recréer la source, et il y aura...

La seule solution que j'ai trouvée est d'utiliser le mot clé record au lieu d'objet.

Voici donc à quoi ressemble le code résultant :

/// used to store and retrieve Words in a sorted array
// - is defined either as an object either as a record, due to a bug
// in Delphi 2010 compiler (at least): this structure is not initialized
// if defined as a record on the stack, but will be as an object
TSortedWordArray = {$ifdef UNICODE}record{$else}object{$endif}
public
  Values: TWordDynArray;
  Count: integer;
  /// add a value into the sorted array
  // - return the index of the new inserted value into the Values[] array
  // - return -(foundindex+1) if this value is already in the Values[] array
  function Add(aValue: Word): PtrInt;
  /// return the index if the supplied value in the Values[] array
  // - return -1 if not found
  function IndexOf(aValue: Word): PtrInt; {$ifdef HASINLINE}inline;{$endif}
end;

En {$ifdef UNICODE}record{$else}object{$endif} est affreux... mais l'erreur de génération de code ne s'est pas produite depuis..

Les modifications du code source qui en résultent ne sont pas énormes, mais un peu décevantes. J'ai découvert que les anciennes versions de l'IDE (par exemple Delphi 6/7) ne sont pas capables d'analyser ce type de déclaration, ce qui fait que la hiérarchie des classes sera brisée dans l'éditeur... :(

La rétrocompatibilité doit inclure des tests de régression. Beaucoup d'utilisateurs de Delphi restent sur ce produit en raison du code existant. La rupture de fonctionnalités est très problématique pour l'avenir de Delphi, à mon avis : si vous devez réécrire beaucoup de code, pourquoi ne pas simplement passer le projet en C# ou Java ?

7voto

Mason Wheeler Points 52022

Object n'était pas la méthode de configuration des objets de Delphi 1 ; il s'agissait de l'éphémère méthode Turbo Pascal de configuration des objets, qui a été remplacée par le modèle Delphi TObject dans Delphi 1. Cette méthode a été conservée pour des raisons de rétrocompatibilité, mais elle doit être évitée pour plusieurs raisons :

  1. Comme vous l'avez noté, il est cassé dans les versions plus récentes. Et AFAIK, il n'est pas prévu de le corriger.
  2. C'est un modèle d'objet conceptuellement incorrect. Le point central de la programmation orientée objet, la seule chose qui la distingue vraiment de la programmation procédurale, est la substitution de Liskov (héritage et polymorphisme), et l'héritage et les types de valeurs ne font pas bon ménage.
  3. Vous perdez la prise en charge d'un grand nombre de fonctionnalités qui nécessitent des descendants de TObject.
  4. Si vous avez vraiment besoin de types de valeurs qui ne doivent pas être alloués et initialisés dynamiquement, vous pouvez utiliser des enregistrements à la place. Vous ne pouvez pas hériter d'eux, mais vous ne pouvez pas faire cela très bien avec object soit, donc vous ne perdez rien ici.

Pour ce qui est du reste de la question, les avantages en termes de vitesse ne sont pas si nombreux. Le modèle TObject est très rapide, surtout si vous utilisez le gestionnaire de mémoire FastMM pour accélérer la création et la destruction des objets, et si vos objets contiennent beaucoup de champs, ils peuvent même être plus rapides que les enregistrements dans de nombreux cas, car ils sont transmis par référence et n'ont pas besoin d'être copiés à chaque appel de fonction.

6voto

Rob Kennedy Points 107381

Lorsque vous avez le choix entre "rapide et peut-être cassé" et "rapide et correct", choisissez toujours le second.

Les objets de style ancien n'offrent pas de gain de vitesse par rapport aux simples enregistrements. Ainsi, si vous êtes tenté d'utiliser des objets de style ancien, vous pouvez utiliser des enregistrements à la place sans risquer d'avoir des types non initialisés gérés par le compilateur ou des méthodes virtuelles cassées. Si votre version de Delphi ne prend pas en charge les enregistrements avec méthodes, utilisez plutôt des procédures autonomes.

1voto

David Heffernan Points 292687

Il y a longtemps, dans les anciennes versions de Delphi qui ne prenaient pas en charge les enregistrements avec des méthodes, l'utilisation de object était le moyen d'obtenir vos objets alloués sur la pile. Très occasionnellement, cela permettait d'obtenir des avantages valables en termes de performances. De nos jours, record est meilleur. La seule caractéristique qui manque à record est la capacité d'hériter d'un autre record .

Vous abandonnez beaucoup de choses quand vous passez de class a record donc ne l'envisagez que si les avantages en termes de performances sont écrasants.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X