Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Pourquoi encore C++ ?

26 réponses
Avatar
jeanmarc.vanel
Je suis pr=EAt =E0 accorder qu'il reste un avantage =E0 C++ pour la
rapidit=E9, de plus en plus marginal d'ailleurs.
Il est vrai aussi que cette avantage se paie par des temps de
d=E9veloppement colossaux.
Alors quelles sont les raisons de la persistence de C++ dans des
projets anciens et nouveaux ?

Je pense que la vraie raison de la persistence de C++ tient =E0 2
facteurs:
- 1. il y a du code h=E9rit=E9 ("legacy"), C et C++
- 2. les mauvaises performances de nombreuses applications Java

Sur le point 2, il me semble que le probl=E8me n'est pas d=FB de mani=E8re
intrins=E8que =E0 Java, mais peut-=EAtre =E0 la JVM ou =E0 la biblioth=E8qu=
e
standard.
Mais surtout il me semble que le facteur "culturel" est tr=E8s
important.
Beaucoup de d=E9veloppeurs Java n'ont pas une conscience claire de o=F9 et
comment la m=E9moire s'alloue et se d=E9salloue, de o=F9 est pass=E9 le tem=
ps
CPU.
Et cela pour une raison bien simple: =E9crire une application Java
utilisable mais peu performante est possible sans cette conscience
claire .
Alors qu'en C/C++ c'est presque impossible.

L'exemple typique d'application o=F9 Java n'a pas perc=E9 est le
navigateur.

6 réponses

1 2 3
Avatar
Mayeul
Christian Laborde a écrit :
jlp a écrit :
Je reconnais que ma réponse n'est pas tout à fait exacte. C'est une
simplification hative. En fait comme dit plus bas, chaque editeur à
son propre algorithme ( Il n'y a qu'à voir comme est géré le GC des
Soft References pour chaque editeur) . Mais quand la collection
devient complexe ( Collections de Collections de ...), voire partagée
par des threads concurrents, il vaut mieux forcer le dé-référencement
dans le code dans une clause finally d'un try/catch quand le besoin est.


Quant au lien sur la derniere strtégie de Garbage ( Garbage First)
elle est encore toute récente et prometeuse pour se rapprocher du
"Temps Reel" par rapport aux strategie GC Parallel / GC Concurrent
plus ancienne.





Quel est le rapport de la clause finally d'un try/catch avec la
destruction d'un objet ?



Vu que c'est ici une opération visant à libérer des ressources, ça me
semble avoir sa place dans un finally, au même titre qu'une fermeture de
stream, qu'une libération de connexion, etc. L'idée étant que ça arrive
qu'il y ait exception ou pas, et quelle que soit l'instruction "return"
atteinte.

Par contre, je suis surpris qu'il existe des GC nécessitant qu'on casse
les cycles et les structures profondes. Ça me semble aller complètement
à l'encontre du design des bibliothèques de base du langage.

--
Mayeul
Avatar
jlp
Mayeul a écrit :
Christian Laborde a écrit :

jlp a écrit :

Je reconnais que ma réponse n'est pas tout à fait exacte. C'est une
simplification hative. En fait comme dit plus bas, chaque editeur à
son propre algorithme ( Il n'y a qu'à voir comme est géré le GC des
Soft References pour chaque editeur) . Mais quand la collection
devient complexe ( Collections de Collections de ...), voire partagée
par des threads concurrents, il vaut mieux forcer le dé-référencement
dans le code dans une clause finally d'un try/catch quand le besoin est.







[SNIP]

Par contre, je suis surpris qu'il existe des GC nécessitant qu'on casse
les cycles et les structures profondes. Ça me semble aller complètement
à l'encontre du design des bibliothèques de base du langage.

--
Mayeul


Oui et je viens m'excuser ici, pour la grosse bétise que j'ai dite au
sujet du non dé-référencement des objets contenus dans une collection
quand on dé-référence directement la Collection. Et à la réflexion,
heureusement que cela marche comme cela...
Ca été signalé par 2 intervenants plus haut dans le fil.

Pour me faire pardonner, vous trouverez là un petit document issus de
tests mettant en jeu une fuite mémoire et ses correctifs, ainsi que la
preuve de ma grosse bétise.
http://pagesperso-orange.fr/jean-louis.pasturel/docs/memoryLeak.pdf
Avatar
Emmanuel Bourg
Samuel Devulder a écrit :

Le soucis est que l'algorithmique est peu comprise de la plupart des
nouveaux programmeurs. Ainsi on voit fleurir ce genre de construction:

List<E> col = ....;
for(int i = 0; i<col.size(); i++) {
E element = col.get(i);
...
}

Or si la liste est une liste dont le size() est coûteux à calculer (par
exemple parce que c'est une liste chaînée écrite à la main), on se
retrouve à avoir du code en N^2 qui ne passe pas à l'échelle. Et même
dans l'absolu si le size() prend un temps constant à calculer, il faut
voir que l'appel d'une méthode est forcément plus coûteux que l'accès à
une variable locale. Aussi il vaudrait mieux écrire:

for(int i=0, max=col.size(); i<max; ++i) {
...
}



Je ne sais pas si l'exemple est bien choisi, il me semble que toutes les
List du JDK mémorisent leur taille pour ne pas avoir à recompter les
éléments à chaque appel de la méthode size(). Donc ce cas précis
l'optimisation suggérée complique la syntaxe et n'apporte rien de
significatif au niveau des performances.
Avatar
Samuel Devulder
Emmanuel Bourg a écrit :

for(int i=0, max=col.size(); i<max; ++i) {
...
}



Je ne sais pas si l'exemple est bien choisi, il me semble que toutes les
List du JDK mémorisent leur taille pour ne pas avoir à recompter les
éléments à chaque appel de la méthode size(). Donc ce cas précis
l'optimisation suggérée complique la syntaxe et n'apporte rien de
significatif au niveau des performances.




C'est vrai. Mais comme on ne peut assumer que size() s'effectue toujours
en temps constant (chacun est libre de retourner sa propre
implementation), cette optim est bienvenue.

Comme je l'ai dit aussi cela dépend du contexte, il n'y a pas pire que
les optims pour les optims. Mais dans le cadre d'applis temps réel ou le
calcul de size() est répété de l'ordre de dizaine de millions de fois,
le fait de ne pas avoir à le calculer et s'épargner un appel à une
fonction peut faire gagner les ms qui manquaient.

Un autre cas ou j'ai vu cela est le fait qu'un algo linéaire se
comportait en N^2 en pratique. Le soucis ici était que le calcul du
size() (bien planqué dans une lib externe) était aussi couteux que
l'itération elle même. Pour trouver cela.. il a suffit d'utiliser un
échantillonnage manuel de la stacktrace de VM via le débuggeur eclipse
et de voir que 9 fois sur 10 le thread était dans size()... Si size() ne
prend pas de temps la proportion aurait du être tout autre. Pour
corriger ce défaut, il aura simplement fallu stocker la size() dans une
variable locale de la boucle.

Dans le même style, en regardant les sources du jdk ils font remarquer
que l'accès à un champ d'instance est plus long que l'accès à une
variable locale. Aussi quand cela est nécessaire, ils font une copie du
champ dans une variable locale pour gagner quelques cycles. Par ailleurs
la copie dans la variable locale permet des optimisations que le
compilateur ne peut faire quand on adresse un champ d'instance. En
effet, un champ d'instance est intrinsèquement volatile (il peut être
modifié par un autre thread) et donc sa valeur ne peut être inférée
constante. Cela est totalement d'une variable locale java qui ne peut
être modifiée à l'extérieur du bloc ou elle est définie. Le compilo peut
utiliser cela pour optimiser le code.

Bien entendu ces petites optims n'ont de sens que si elles sont répétées
des millions de fois (les petits ruisseaux).. Si on veut gagner des
ordres de grandeur il vaut mieux changer son algorithme et peut être
utiliser un peu plus de mémoire pour stocker des choses pré calculées.
En fait tout dépend de l'application et des contraintes correspondantes.

sam.
Avatar
Samuel Devulder
Emmanuel Bourg a écrit :

for(int i=0, max=col.size(); i<max; ++i) {
...
}



Je ne sais pas si l'exemple est bien choisi, il me semble que toutes les
List du JDK mémorisent leur taille pour ne pas avoir à recompter les
éléments à chaque appel de la méthode size(). Donc ce cas précis
l'optimisation suggérée complique la syntaxe et n'apporte rien de
significatif au niveau des performances.




C'est vrai. Mais comme on ne peut assumer que size() s'effectue toujours
en temps constant (chacun est libre de retourner sa propre
implementation), cette optim est bienvenue.

Comme je l'ai dit aussi cela dépend du contexte, il n'y a pas pire que
les optims pour les optims. Mais dans le cadre d'applis temps réel ou le
calcul de size() est répété de l'ordre de dizaine de millions de fois,
le fait de ne pas avoir à le calculer et s'épargner un appel à une
fonction peut faire gagner les ms qui manquaient.

Un autre cas ou j'ai vu cela est le fait qu'un algo linéaire se
comportait en N^2 en pratique. Le soucis ici était que le calcul du
size() (bien planqué dans une lib externe) était aussi couteux que
l'itération elle même. Pour trouver cela.. il a suffit d'utiliser un
échantillonnage manuel de la stacktrace de VM via le débuggeur eclipse
et de voir que 9 fois sur 10 le thread était dans size()... Si size() ne
prend pas de temps la proportion aurait du être tout autre. Pour
corriger ce défaut, il aura simplement fallu stocker la size() dans une
variable locale de la boucle.

Dans le même style, en regardant les sources du jdk ils font remarquer
que l'accès à un champ d'instance est plus long que l'accès à une
variable locale. Aussi quand cela est nécessaire, ils font une copie du
champ dans une variable locale pour gagner quelques cycles. Par ailleurs
la copie dans la variable locale permet des optimisations que le
compilateur ne peut faire quand on adresse un champ d'instance. En
effet, un champ d'instance est intrinsèquement volatile (il peut être
modifié par un autre thread) et donc sa valeur ne peut être inférée
constante. Cela est totalement d'une variable locale java qui ne peut
être modifiée à l'extérieur du bloc ou elle est définie. Le compilo peut
utiliser cela pour optimiser le code.

Bien entendu ces petites optims n'ont de sens que si elles sont répétées
des millions de fois (les petits ruisseaux).. Si on veut gagner des
ordres de grandeur il vaut mieux changer son algorithme et peut être
utiliser un peu plus de mémoire pour stocker des choses pré calculées.
En fait tout dépend de l'application et des contraintes correspondantes.

sam.
Avatar
Mayeul
Samuel Devulder a écrit :
Emmanuel Bourg a écrit :

for(int i=0, max=col.size(); i<max; ++i) {
...
}



Je ne sais pas si l'exemple est bien choisi, il me semble que toutes
les List du JDK mémorisent leur taille pour ne pas avoir à recompter
les éléments à chaque appel de la méthode size(). Donc ce cas précis
l'optimisation suggérée complique la syntaxe et n'apporte rien de
significatif au niveau des performances.




C'est vrai. Mais comme on ne peut assumer que size() s'effectue toujours
en temps constant (chacun est libre de retourner sa propre
implementation), cette optim est bienvenue.



Chacun est libre de faire sa propre implémentation, mais un size() en
temps non constant me semble être un manque flagrant de considération
pour l'usage de cette méthode. Le problème se situerait là, plus que
dans le fait de rappeler size() à chaque tour de boucle.

Après, bon, on a pas toujours la possibilité de modifier
l'implémentation fautive, et puis de toute façon un appel de méthode est
plus coûteux qu'un accès à une variable locale.

--
Mayeul
1 2 3