Quand à "thoughts of Cameleon", on ne voit pas le code sur lequel il compare ObjC et C++. Tout cela ne me semble pas très scientifique, et dans ce genre d'études de cas particuliers, tout et son contraire peut être dit.
First I wrote a mini benchmark -- get a very basic object (a couple of ivars, one method "plop" assigning a value to an ivar), and then create an instance, initialize it (call the init method), call the one method the object has, then deallocate the object. 10000000 times.
Il manque quoi pour savoir ligne pour ligne le code (aux noms de variables près) ? :P
Et je ne suis toujours pas d'accord sur le fait qu'ObjC n'est fait que pour les interfaces, le système de messages n'est pas fait pour les traitements lourds, ça oui, mais tous les traitements moins lourds où ObjC n'est pas un problème s'étendent au-delà de la simple interface... Sinon, quel intérêt d'avoir séparé Foundation et AppKit.
Oui le mot "interface" que j'ai utilisé est réducteur. La ligne de partage est évidemment moins nette, et elle évolue dans le sens favorable à Objective-C : c'est ce que tu m'avais fait comprendre dans un post précédent.
Ce code marche... En 32 bits ; en 64, le compilateur va t'emmerder, il va te dire que @defs n'est plus utilisable dans le nouvel ABI, mais c'est le seul problème, après tu peux toujours utiliser des structures intelligentes, mais tu dois définir la structure manuellement, ce qui peut être emmerdant...
Mais oui, tu peux envoyer des messages à n'importe quoi, il suffit juste qu'il y ait dans les premiers octets un pointeur sur un objet Class, et tu peux à partir de là , coller n'importe quoi.
Mais oui, tu peux envoyer des messages à n'importe quoi, il suffit juste qu'il y ait dans les premiers octets un pointeur sur un objet Class, et tu peux à partir de là , coller n'importe quoi.
C'est exactement un exemple de ce que je veux dire pour "faire du C dans de l'Objective-C", mais mes mots sont peut-être mal choisis ... On peut avec l'Objective-C jouer sur les deux tableaux (et pas seulement sur deux tableaux) et c'est ce qui me plait dans Obj-C
Plus généralement, c'est l'idée de pouvoir rentrer dans le fonctionnement à n'importe quel niveau, à n'importe quelle profondeur, sans avoir besoin de changer de langage.
Vous n'aurez aucun warning, et ça marchera sans problème, vous pourrez appeler des méthodes sans cast sans déréférencement, par contre évitez bien sûr les retain, release, et malheureusement vous ne pouvez pas non plus retourner l'objet...
Mais voici le code qui vous permettra d'utiliser, de façon crade bien sûr, les objets alloués sur la pile en Objective-C comme n'importe quel objet C++ :
$$(nom de la classe) : vous donne le type sur la pile de l'objet.
$$$(nom de l'objet) : vous donne le nom de l'objet sur la pile, c'est celui qu'il faut retourner.
$$$$(nom de la classe) : crée le type de la pile, il faut donc le faire juste après la création de la classe pour fonctionner.
$(nom de la classe, nom de l'objet) : crée la variable sur la pile, vous ne pouvez pas envoyer de message directement à cette déclaration, et "nom de l'objet" sera le receveur des messages.
J'ai presque honte d'avoir écrit ce code. Cependant, ça prouve que la création des objets sur le tas uniquement est un choix, plus qu'une réelle contrainte... Sachant que beaucoup de classes de Cocoa ne peuvent pas fonctionner comme ça.
PS : Le nombre de $ correspond à l'ordre dans lequel j'ai imaginé les macros. ;D
% time ./testObjc<br />./testObjc 3,80s user 0,01s system 64% cpu 5,905 total
% time ./testCpp<br />./testCpp 0,24s user 0,00s system 72% cpu 0,335 total
J'ai les mêmes résultats que lui... facteur 16.
Quand on fait ce genre de benchs, on n'active pas l'optimisation, sinon on a des résultats faux. Si j'active les optimisations -O3, j'ai un facteur de 728... ::) (le compilo C++ apparemment vire le code inutile, donc c'est immédiat ; par contre, l'optimisation c'est pas le fort du compilo Obj-C apparemment...)
Voici l'implémentation finale de la création d'objets Objective-C sur la pile. Cette implémentation permet aux objets de recevoir les messages retain, release, retainCount, copy et mutableCopy. De plus, il est possible de transformer l'objet sur la pile en objet sur le tas avec la méthode __unstack.
Avec les classes clusters, ça ne marchera clairement pas, puisque la classe publique d'un class-cluster est abstraite, et ne définit aucune variable d'instance... Il faudrait avoir accès aux tailles des sous classe concernée dès la compilation, ce qui n'est pas possible puisqu'on n'a pas leur interface...
Il manque quoi pour savoir ligne pour ligne le code (aux noms de variables près) ? :P
Le code et les options de compilation
#include <stdio.h> #define GIGA 1000000000 int main(void) { int i,a=5,b=2,aux; for(i=0;i<GIGA;i++) { aux=b;b=a;a=aux; } } % gcc pgm.c -o pgm -Os % time pgm
real 0m0.513s user 0m0.507s sys 0m0.004s
===================================
#include <stdio.h> #define GIGA 1000000000 int main(void) { int i,a=5,b=2,aux; for(i=0;i<GIGA;i++) { aux=b;b=a;a=aux; } printf("%d\n",a); }
% gcc pgm.c -o pgm -Os % time pgm 5
real 0m1.529s user 0m1.508s sys 0m0.009s %
Je ne sais pas comment vous faite pour avoir les temps d'exécution ? j'imagine l'option time pgm, mais comment vous faites ? Vous passez par un fichier make, non ? Sinon en plaçant des NSLOG en début et fin de code (obj-c et xcode) pour voir sur mon macbook je remarque un facteur de 5 en compilant avec LLVM et en activant quelques options... C'était juste pour dire...
Je ne sais pas comment vous faite pour avoir les temps d'exécution ? j'imagine l'option time pgm, mais comment vous faites ? Vous passez par un fichier make, non ?
Pas besoin de make, d'ailleurs je n'ai jamais eu besoin d'utiliser make sous mac, XCode le fait à notre place. time ./pgm lance l'exécution de ./pgm sous l'exécutable time, ce dernier se chargeant de mesurer les durées attribuées au système et au programme.
Toto *t = new Toto;<br /> t->plop(1);<br /> delete t;
J'obtiens encore le même résultat que lui :
./testCpp  1,16s user 0,00s system 64% cpu 1,806 total
facteur 3
Cet exemple est extrêmement sensible ... Mais quelle interprétation donner aux essais suivants et à ceux de Schlum : est-ce une question de pile/tas , ou d'optimisation cachée, ou de quantité de mémoire utilisable nécessitant des déblocages successifs selon le compilateur et l'environnement, ...
Quel exécutable time ? Je ne le vois ni dans les menus xcode, ni dans les applications developer ni dans les utilitaires ???
Tu ouvres le terminal (dans /Applications/Utilitaires) Tu y tapes cd ~/Desktop Pour vérification, tapes ls tu dois y voir la liste des fichiers sur ton bureau. Tu crées un fichier pgm.c (avec TextEdit ou XCode) que tu enregistres sur le bureau Puis tu tapes les instructions de ce post.
time est un fichier binaire exécutable qui doit se trouver dans /usr/bin/ , on peut le voir en tapant dans le terminal ls /usr/bin/t*
Pour le -O3, l'optimisation a dû virer le code... Pour le reste, tu travailles avec des structures et des pointeurs de fonctions ; c'est pas vraiment comparable (même si les objets sont des structures spéciales...)
Quel exécutable time ? Je ne le vois ni dans les menus xcode, ni dans les applications developer ni dans les utilitaires ???
Tu ouvres le terminal (dans /Applications/Utilitaires) Tu y tapes cd ~/Desktop Pour vérification, tapes ls tu dois y voir la liste des fichiers sur ton bureau. Tu crées un fichier pgm.c (avec TextEdit ou XCode) que tu enregistres sur le bureau Puis tu tapes les instructions de ce post.
time est un fichier binaire exécutable qui doit se trouver dans /usr/bin/ , on peut le voir en tapant dans le terminal ls /usr/bin/t*
Ok j'ai enfin compris, et surtout j'ai encore appris quelque chose ... MERCI
Les compilateurs doivent bien faire certaines optimisations standards, sinon comment comprendre les différences entre tous ces essais ?
dans 1229159116:
Pour le reste, tu travailles avec des structures et des pointeurs de fonctions ; c'est pas vraiment comparable (même si les objets sont des structures spéciales...)
Je sais assez peu de choses en ce domaine, mais tu vois autre chose dans un objet qu'une telle structure C avec quelques pointeurs en plus pour régler les problèmes liées au fonctionnement spécifique (héritages, ...) ? Le compilateur C++ n'est pas fait en C ?
Je ne vois pas pourquoi faire rentrer en jeu des pseudo optimisations du compilateur (à part quand on compile avec -O3 et qu'il vire tout le code inutile)... Tous ces exemples sont logiques ???
Et encore une fois ça ne sert à rien de faire des bench avec -O3 ; tu ne sais pas ce qu'il bidouille derrière... Il enlève ou raccourcit le code qu'il a calculé comme inutile etc. :P
Et encore une fois ça ne sert à rien de faire des bench avec -O3 ; tu ne sais pas ce qu'il bidouille derrière... Il enlève ou raccourcit le code qu'il a calculé comme inutile etc. :P
Oui mais quelle est la part d'optimisation sans aucune option ?
dans 1229161419:
Je ne vois pas pourquoi faire rentrer en jeu des pseudo optimisations du compilateur (à part quand on compile avec -O3 et qu'il vire tout le code inutile)... Tous ces exemples sont logiques ???
Tu trouves logique la multiplication par 7 pour exécuter une boucle vide après optimisation ?? acceptable comme erreur, mais logique ?
int main(void) { unsigned long i,end =(unsigned long)1000*1000*10; i=end; }
Ben c'est qu'il n'enlève pas toute la boucle à priori... Il estime qu'il a besoin des calculs qui sont faits dans le corps du "for()" je suppose. Par contre, avec ou sans contenu, ça donne le même temps, donc il enlève le contenu.
Tu trouves logique la multiplication par 7 pour exécuter une boucle vide après optimisation ?? acceptable comme erreur, mais logique ?
Il faudrait voir le code assembleur pour voir la différence et voir se qu'il fait réellement, branchement court par exemple, le fait de faire tenir en cache la boucle, il pourrait dans un cas faire un branchement et dans l'autre avoir le code à la suite, il peut dans un cas tenir compte de l'adresse mémoire paire et pas dans l'autre, etc... Note: lubrifier les pipelines, peut-être ?
Après avoir tout lu, je suis relativement d'accord avec Ali sur le fait que qui peu le plus peu le moins au niveau des nouvelles fonctionnalités d'Obj-C. Perso, j'aime bien les properties car elles allègent le code et comme cela a été souligné rien n'empêche de surcharger une méthode si nécessaire. Je trouve juste le @synthesize et l'obligation de déclarer la variable d'instance qui va en face de trop finalement.
Je suis par contre surpris que le parallèle entre les classes de Cocoa et de Core Foundation n'est pas été plus marqué dans les propos. Notamment, si l'on aborde le sujet sensible de la perte de performance d'Obj-C par rapport à des langages plus statiques. La plupart des objets de Cocoa peuvent en effet être castés vers leurs homologues de Core Foundation. On se retrouve alors à manipuler, tableaux, dictionaires, etc directement en C. Pour des boucles assez critiques, cela me semble plus efficace que d'essayer de gagner du temps (ou plutôt d'en perdre moins) en passant par des mises en cache de certains appels.
Pour les différences de performances entre tas et pile, c'est un problème vieux comme le monde. L'allocation sur le tas a tout de même de grosses contraintes vu le dynamisme des langages objet. Bien sûr, sur une boucle de 3 lignes ça "déchire sa race" mais dans la vraie vie de tous les jours? Je ne connais pas beaucoup de programme en C qui fasse l'usage d'alloca par exemple. Et il me semble que l'usage de pointeurs sur des structures de données permet de limiter très largement les conséquences de la pile puisqu'on peut ainsi taper n'importe où dans la mémoire mais je me trompe peut-être.
Question optimisations de compilation, je rejoins Schlum. Il ne faut pas espérer de miracle. Les appels de méthodes en Obj-C sont lent point barre. C'est le talon d'Achille de ce langage d'où mon allusions à Core Foundation pour palier à cette limite sans devoir réinventer le C.
En tout cas, c'est une discussion très intéressante comme je les aime sur ce forum. :P
Je suis par contre surpris que le parallèle entre les classes de Cocoa et de Core Foundation n'est pas été plus marqué dans les propos. Notamment, si l'on aborde le sujet sensible de la perte de performance d'Obj-C par rapport à des langages plus statiques. La plupart des objets de Cocoa peuvent en effet être castés vers leurs homologues de Core Foundation. ... C'est le talon d'Achille de ce langage d'où mon allusion à Core Foundation pour palier à cette limite sans devoir réinventer le C.
Pourquoi pas, mais il serait temps que le ménage soit fait entre Carbon, CoreFoundation, CoreGraphics, etc ... et toutes ses librairies C qui se chevauchent, on finit par ne plus savoir quelle ressource prendre pour optimiser son code. Ok pour une belle librairie C à utiliser sous Objective-C, mais unifiée, et avec une logique documentaire telle qu'on l'apprécie habituellement avec les outils XCode. Tiens, ça me rappelle une autre discussion il y a deux ou trois mois où je disais que le remplacement du C de Carbon par Obj-C n'était qu'une question de dénomination des routines C ...
Réponses
Il manque quoi pour savoir ligne pour ligne le code (aux noms de variables près) ? :P
Ce code marche... En 32 bits ; en 64, le compilateur va t'emmerder, il va te dire que @defs n'est plus utilisable dans le nouvel ABI, mais c'est le seul problème, après tu peux toujours utiliser des structures intelligentes, mais tu dois définir la structure manuellement, ce qui peut être emmerdant...
Mais oui, tu peux envoyer des messages à n'importe quoi, il suffit juste qu'il y ait dans les premiers octets un pointeur sur un objet Class, et tu peux à partir de là , coller n'importe quoi.
Par exemple, un tableau de char :
Et après on dénigre le C++ :P
Le code et les options de compilation
#include <stdio.h>
#define GIGA 1000000000
int main(void) {
int i,a=5,b=2,aux;
for(i=0;i<GIGA;i++)
{
aux=b;b=a;a=aux;
}
}
% gcc pgm.c -o pgm -Os
% time pgm
real 0m0.513s
user 0m0.507s
sys 0m0.004s
===================================
#include <stdio.h>
#define GIGA 1000000000
int main(void) {
int i,a=5,b=2,aux;
for(i=0;i<GIGA;i++)
{
aux=b;b=a;a=aux;
}
printf("%d\n",a);
}
% gcc pgm.c -o pgm -Os
% time pgm
5
real 0m1.529s
user 0m1.508s
sys 0m0.009s
%
C'est exactement un exemple de ce que je veux dire pour "faire du C dans de l'Objective-C",
mais mes mots sont peut-être mal choisis ...
On peut avec l'Objective-C jouer sur les deux tableaux (et pas seulement sur deux tableaux) et c'est ce qui me plait dans Obj-C
Plus généralement, c'est l'idée de pouvoir rentrer dans le fonctionnement à n'importe quel niveau, à n'importe quelle profondeur, sans avoir besoin de changer de langage.
Vous n'aurez aucun warning, et ça marchera sans problème, vous pourrez appeler des méthodes sans cast sans déréférencement, par contre évitez bien sûr les retain, release, et malheureusement vous ne pouvez pas non plus retourner l'objet...
Mais voici le code qui vous permettra d'utiliser, de façon crade bien sûr, les objets alloués sur la pile en Objective-C comme n'importe quel objet C++ :
Alors, quelques explications :
J'ai presque honte d'avoir écrit ce code. Cependant, ça prouve que la création des objets sur le tas uniquement est un choix, plus qu'une réelle contrainte... Sachant que beaucoup de classes de Cocoa ne peuvent pas fonctionner comme ça.
PS : Le nombre de $ correspond à l'ordre dans lequel j'ai imaginé les macros. ;D
Test.cpp :
Compilation :
g++ -O0 -W -Wall -o testCpp test.cpp
gcc -O0 -W -Wall -framework Foundation -o testObjc test.m
Exécution :
J'ai les mêmes résultats que lui... facteur 16.
Quand on fait ce genre de benchs, on n'active pas l'optimisation, sinon on a des résultats faux.
Si j'active les optimisations -O3, j'ai un facteur de 728... ::) (le compilo C++ apparemment vire le code inutile, donc c'est immédiat ; par contre, l'optimisation c'est pas le fort du compilo Obj-C apparemment...)
J'obtiens encore le même résultat que lui :
facteur 3
Cette implémentation permet aux objets de recevoir les messages retain, release, retainCount, copy et mutableCopy.
De plus, il est possible de transformer l'objet sur la pile en objet sur le tas avec la méthode __unstack.
Le code final fait 27 lignes. ;D
Maintenant il ne reste plus qu'à l'implémenter dans le compilateur...
Je ne sais pas comment vous faite pour avoir les temps d'exécution ? j'imagine l'option time pgm, mais comment vous faites ? Vous passez par un fichier make, non ? Sinon en plaçant des NSLOG en début et fin de code (obj-c et xcode) pour voir sur mon macbook je remarque un facteur de 5 en compilant avec LLVM et en activant quelques options... C'était juste pour dire...
Pas besoin de make, d'ailleurs je n'ai jamais eu besoin d'utiliser make sous mac, XCode le fait à notre place.
time ./pgm lance l'exécution de ./pgm sous l'exécutable time, ce dernier se chargeant de mesurer les durées attribuées au système et au programme.
Cet exemple est extrêmement sensible ...
Mais quelle interprétation donner aux essais suivants et à ceux de Schlum : est-ce une question de pile/tas , ou d'optimisation cachée, ou de quantité de mémoire utilisable nécessitant des déblocages successifs selon le compilateur et l'environnement, ...
Voici un essai en C
% gcc pgm3.c -o pgm
% time pgm
real 0m0.085s
user 0m0.083s
sys 0m0.002s
%
3 fois moins que C++
ou cette autre version :
% gcc pgm.c -o pgm
% time pgm
real 0m0.379s
user 0m0.360s
sys 0m0.003s
% gcc pgm.c -o pgm -O3
% time pgm
real 0m0.009s
user 0m0.007s # l'optimisation détruit le contenu de la boucle
sys 0m0.002s
% gcc pgm3.c -o pgm
% time pgm
real 0m1.021s
user 0m1.012s
sys 0m0.005s
%
et à la méthode anti-belge  // sans free(t)
% gcc pgm3.c -o pgm
% time pgm
real 0m1.060s
user 0m0.878s
sys 0m0.175s
Tu ouvres le terminal (dans /Applications/Utilitaires)
Tu y tapes cd ~/Desktop
Pour vérification, tapes ls tu dois y voir la liste des fichiers sur ton bureau.
Tu crées un fichier pgm.c (avec TextEdit ou XCode) que tu enregistres sur le bureau
Puis tu tapes les instructions de ce post.
time est un fichier binaire exécutable qui doit se trouver dans /usr/bin/ , on peut le voir en tapant dans le terminal ls /usr/bin/t*
Pour le reste, tu travailles avec des structures et des pointeurs de fonctions ; c'est pas vraiment comparable (même si les objets sont des structures spéciales...)
Ok j'ai enfin compris, et surtout j'ai encore appris quelque chose ... MERCI
Les compilateurs doivent bien faire certaines optimisations standards, sinon comment comprendre les différences entre tous ces essais ?
Je sais assez peu de choses en ce domaine, mais tu vois autre chose dans un objet qu'une telle structure C avec quelques pointeurs en plus pour régler les problèmes liées au fonctionnement spécifique (héritages, ...) ? Le compilateur C++ n'est pas fait en C ?
Sans aucune boucle
% gcc pgm34.c -o pgm -O3
% time pgm
real 0m0.003s
user 0m0.001s
sys 0m0.002s
%
Avec une boucle vide
% gcc pgm34.c -o pgm -O3
% time pgm
real 0m0.010s
user 0m0.007s
sys 0m0.002s
%
:P
Oui mais quelle est la part d'optimisation sans aucune option ?
Tu trouves logique la multiplication par 7 pour exécuter une boucle vide après optimisation ?? acceptable comme erreur, mais logique ?
int main(void) {
unsigned long i,end =(unsigned long)1000*1000*10;
i=end;
}
user 0m0.001s
Par contre, avec ou sans contenu, ça donne le même temps, donc il enlève le contenu.
Avec -O0 il n'y a aucune optimisation...
Il faudrait voir le code assembleur pour voir la différence et voir se qu'il fait réellement, branchement court par exemple, le fait de faire tenir en cache la boucle, il pourrait dans un cas faire un branchement et dans l'autre avoir le code à la suite, il peut dans un cas tenir compte de l'adresse mémoire paire et pas dans l'autre, etc...
Note: lubrifier les pipelines, peut-être ?
Après avoir tout lu, je suis relativement d'accord avec Ali sur le fait que qui peu le plus peu le moins au niveau des nouvelles fonctionnalités d'Obj-C. Perso, j'aime bien les properties car elles allègent le code et comme cela a été souligné rien n'empêche de surcharger une méthode si nécessaire. Je trouve juste le @synthesize et l'obligation de déclarer la variable d'instance qui va en face de trop finalement.
Je suis par contre surpris que le parallèle entre les classes de Cocoa et de Core Foundation n'est pas été plus marqué dans les propos. Notamment, si l'on aborde le sujet sensible de la perte de performance d'Obj-C par rapport à des langages plus statiques. La plupart des objets de Cocoa peuvent en effet être castés vers leurs homologues de Core Foundation. On se retrouve alors à manipuler, tableaux, dictionaires, etc directement en C. Pour des boucles assez critiques, cela me semble plus efficace que d'essayer de gagner du temps (ou plutôt d'en perdre moins) en passant par des mises en cache de certains appels.
Pour les différences de performances entre tas et pile, c'est un problème vieux comme le monde. L'allocation sur le tas a tout de même de grosses contraintes vu le dynamisme des langages objet. Bien sûr, sur une boucle de 3 lignes ça "déchire sa race" mais dans la vraie vie de tous les jours? Je ne connais pas beaucoup de programme en C qui fasse l'usage d'alloca par exemple. Et il me semble que l'usage de pointeurs sur des structures de données permet de limiter très largement les conséquences de la pile puisqu'on peut ainsi taper n'importe où dans la mémoire mais je me trompe peut-être.
Question optimisations de compilation, je rejoins Schlum. Il ne faut pas espérer de miracle. Les appels de méthodes en Obj-C sont lent point barre. C'est le talon d'Achille de ce langage d'où mon allusions à Core Foundation pour palier à cette limite sans devoir réinventer le C.
En tout cas, c'est une discussion très intéressante comme je les aime sur ce forum. :P
Pourquoi pas, mais il serait temps que le ménage soit fait entre Carbon, CoreFoundation, CoreGraphics, etc ... et toutes ses librairies C qui se chevauchent, on finit par ne plus savoir quelle ressource prendre pour optimiser son code. Ok pour une belle librairie C à utiliser sous Objective-C, mais unifiée, et avec une logique documentaire telle qu'on l'apprécie habituellement avec les outils XCode.
Tiens, ça me rappelle une autre discussion il y a deux ou trois mois où je disais que le remplacement du C de Carbon par Obj-C n'était qu'une question de dénomination des routines C ...
Les autres frameworks ont été conçus plus ou moins autour de Cocoa.