Partage
  • Partager sur Facebook
  • Partager sur Twitter

Pourquoi vous faites des allocations dynamiques ?

8 juillet 2022 à 13:06:35

Coucou j'espère que vous allez bien depuis la dernière fois et que je vous ai pas trop manqué, je reviens par ici car il y a quelque chose qui me turlupine (en fait il y a 2 choses mais là on va parler que d'une seule sinon ça va repartir dans un topic à rallonge).

Je ne comprends pas pourquoi vous trouvez que c'est bien de faire des allocations dynamiques de partout. Déjà c'est une opération qui peut échouer, donc avoir des opérations comme ça en plein milieu c'est dommage, et ensuite ça oblige à avoir un code dégueu car comme vous l'avez dit, "un code correct c'est un code avec un if par ligne". Du coup, fort heureusement, en C++ il existe des mécanismes pour éviter de rendre le code dégueu, comme le RAII, les conteneurs, les pointeurs intelligents, les exceptions. Les pointeurs intelligents et conteneurs, associés au RAII, permettent de ne pas s'occuper explicitement de la libération des ressources et les exceptions permettent d'éviter d'avoir un if par ligne, en permettant de faire remonter l'erreur à qui veut bien la gérer, et donc permet d'éviter de gérer chaque erreur localement avec des if partout (bien que ça se discute, il vaut peut être mieux gérer les erreurs au plus proche de là où elles se produisent mais bon, ça permet aussi de gérer des erreurs qui se produisent dans les constructeurs, du coup ça "s'accorde bien" avec le reste de la philosophie du C++).

Donc tout ça pour dire que en fait, en C++ moderne, on fait toujours "n'importe quoi" comme en C, à savoir faire des allocations de partout etc, mais au moins en C++ le code est plus simple et plus safe. Mais le problème c'est que ça ne résout pas le problème initial ! Tout ce que fait le C++ moderne c'est mettre du sucre par dessus un gateau C dégueu. Autrement dit, le code C++ est clairement plus propre que la même chose écrite en C, mais au final il se passe la même chose en dessous, à savoir que les allocations peuvent foirer, etc. Donc en fait ça ne rend pas le programme plus robuste.

Du coup ce que fait vous-savez-qui, c'est plutôt que de faire "n'importe quoi" mais d'une manière propre et safe avec du C++, il fait en sorte de ne pas faire n'importe quoi en premier lieu ! Du coup c'est pour ça que lui (et d'autres) peuvent se passer des concepts de C++ modernes, car le programme est pas codé de la même manière.

Et c'est là que je comprends pas votre approche moderne, ok vos programmes sont peut être écrits "proprement" et d'une manière lisible, etc, mais il peuvent toujours foirer au final, comme si on le faisait en vieux C. Alors que lui il fait en sorte que le programme ne _puisse pas_ planter en plein milieu. C'est quand même mieux non ?

Du coup c'est quoi qu'il fait ? Et bien allez regarder HH, c'est pas forcément un truc que je peux résumer en 2 phrases. Mais en gros pour la mémoire je l'ai déjà dit plein de fois, il a une ou plusieurs "zones mémoire" qu'il appelle des "memory arena" allouée à l'avance (ou qui peut grossir sur mesure au besoin), à partir de laquelle il peut récupérer de la mémoire sur commande en étant sûr qu'elle y est, donc ça fait que ce n'est pas la peine de vérifier les allocations (donc ça simplifie le code) et ça rend le programme plus robuste puisqu'il ne peut pas planter à cause de ça.

Regardez ce qu'il dit dans cet extrait, notamment de 0:00 à 5:35, 7:35 à 13:18 et de 59:20 à 1:03:31 : https://guide.handmadehero.org/code/day014/  (et vous pouvez aussi regarder les questions si vous vous posez les mêmes, pour voir ce qu'il répond)

Il dit que l'allocation dynamique c'est se mentir à soi même.

Allez bien regarder ces extraits, c'est par rapport à ça que je veux une réponse, pas par rapport à mon résumé qui est peut être pas tout à fait exact.

Donc vous avez raison que le C old school c'est dégueu et que le C++ moderne permet de faire la même chose en plus propre et safe, mais le problème c'est que ça fait la même chose au final, donc ça peut toujours planter en plein milieu, etc, ça ne résout pas réellement le problème, alors qu'avec sa manière de faire, non seulement le code s'approche du C++ en terme de clarté et simplicité (à savoir ne pas avoir à libérer à la main, ne pas avoir à tester chaque ligne, etc) mais tout en évitant le coeur du problème de faire des allocations partout, qui peuvent foirer, fragmenter la mémoire, etc.

Et en fait vous allez être content, Lynix avait raison quand il disait que Casey serait pas fier de moi s'il savait que je propage ses idées à droite à gauche. Parce que en fait Casey tout ce qu'il dit, c'est sur ses propres lives, il ne va pas chez les autres pour leur dire qu'ils font de la **** ou autre, et il veut pas influencer les autres pour qu'il fasse les choses d'une certaine manière. En fait c'est même pire que ça, il déteste cet état d'esprit, du coup il aurait honte de moi en fait :(

Il le dit ici https://guide.handmadehero.org/code/day630/#8184 

"the idea is not to convince people to do what I do, it's to provide a series for the people who want to do it"
"I don't like telling other people what to do"
"you've never seen me go to someone's stream and complain about the programming language they were using, because I don't understand why someone would ever do that, it's absolutely nuts to me [...] that's horrible I can't understand"
"I don't want Handmade Hero to be that, I don't want people to interpret Handmade Hero as me telling people not to or to do something"
"I really don't want Handmade Hero to be about proselytising..."

Et même dans les extraits que j'ai donné plus haut sur la gestion mémoire (à 5:15), en fait on se rend compte qu'il pensait comme ça depuis le début et qu'il ne veut pas forcer les gens à faire quoi que ce soit : "I like to show different ways to do things, I'm not trying to tell you to do it this way, like I say on a lot of streams, I'm just telling you how I like to do it [...] it's good to have different perspectives"

Alors que moi je croyais qu'il fallait convaincre tout le monde de faire comme lui mais en fait ce n'est pas du tout ce qu'il veut :(

-
Edité par JadeSalina 8 juillet 2022 à 13:09:18

  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2022 à 13:31:39

Salut,

Je me suis arrêté à "Du coup ce que fait vous-savez-qui".

Maintenant, d'une part en C++ moderne on évite les allocations à la main, mais on utilise des objets qui en font en interne (std::vector par exemple). Et oui, on a parfois besoin d'allouer un espace qu'on ne connaît pas à la compilation !

Après, il faut aussi essayer de réduire le nombre d'allocs, de préférer une grosse alloc à plein de petites, ça oui.

Après, tu as aussi la philosophie de l'embarqué ou tu alloues une fois pour toutes toute la mémoire dont tu auras besoin, puis tu tapes dedans directement, ce qui nécessite de bien planifier sa mémoire...

Mais ne pas faire d'allocation dynamique du tout, j'avoue que ça m'échappe. 

Je te prends un exemple simple : tu fais un programme qui demande combien tu as de personnes, puis pour chaque personne demande leur age, puis à la fin te redonne l'age de tout le monde. Toi et ton idole complexé vous faites comment sans allocation dynamique ? J'aimerais bien le savoir...

  • Partager sur Facebook
  • Partager sur Twitter

Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

8 juillet 2022 à 13:40:00

> Je ne comprends pas pourquoi vous trouvez que c'est bien de faire des allocations dynamiques de partout

Qui a dit une bêtise (je reste poli)  pareille? Moins il y a d'allocs mieux on se porte. Tu pars de mauvaises prémices.

> un if par ligne

un if toutes les **deux* lignes -- il faut bien un peu de "code utile" au milieu du code de reroutage des flots en échec. Cela correspond à l'heuristique de Raymond Chen pour reconnaître du premier coup d'oeil un code C qui n'est pas correct -- cf son billet de blog sur le sujet tout ça...

> en C++ moderne, on fait toujours "n'importe quoi" comme en C, à savoir faire des allocations de partout etc

Absolument pas. Le langage, et encore plus dans les styles modernes est orienté valeur => pas d'alloc si on peut les éviter.

Autres mauvaises prémices, (et j'ai la flemme de rentrer dans les détails), c'est de croire qu'une exception signifie planter -- j'ai vu passer les mêmes a priori (sous-entendu: c'est faux) sur r/cpp en milieu de semaine. Une exception signifie qu'une situation indépendante de notre volonté fait qu'un traitement ne peut pas aboutir et qu'il faut que l'on passe à autre chose. Ce n'est pas la fin du monde. On dit juste stop, on arrête ce que l'on a commencé, on range derrière soit, et on fait autre chose. Un code correct range derrière lui et pourra tourner h24 7j/7 sans s’arrêter, d'autres codes préfèrent planter car c'est trop dur (pauvres choupinous) de réfléchir à ce que l'on fait quand le monde est trop inzuste et nous tarte d'erreurs.

  • Partager sur Facebook
  • Partager sur Twitter
C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
8 juillet 2022 à 13:52:36

Bon, je passe sur les details de la video , mais pour répondre à ta question: "Pourquoi faire des allocations dynamiques ?"

Simplement parceque la mémoire disponible sur le stack est minuscule (+/- 1Mo ?), et que l'espace disponible ne saurait suffire à n'importe quel programme un temps soit peut évolué (Hello World, ca ne compte pas, on est d'accord ?)

Sachant que la taille du stack est très limitée, comment vas-tu charger tes ressources qui font plusieurs dizaines voir centaines de Mo ?
Tu ne peux pas raisonnablement "reserver" un espace maximum, d'une part ce maximum est inconnu, d'autre part ca bouffe de la place inutilement (lire: c'est une gestion calamiteuse de la memoire).

Enfin, rend toi bien compte que Casey utilise les API Windows, et que même s'il n'écrit pas d'allocation dynamique de façon explicite, les API vont le faire à sa place.

Bref, les allocations dynamiques sont obligatoires, à condition de les faire correctement.
Oui, ca peut échouer, et c'est la responsabilité de programmeur de prendre cela en charge pour que le programme puisse reprendre un comportement propre (c'est la que le mot "correctement" prend tout son sens).

  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2022 à 14:14:41

J'ai arrêté dès que ctrl+f a trouvé Casey.
  • Partager sur Facebook
  • Partager sur Twitter

git is great because Linus did it, mercurial is better because he didn't.

8 juillet 2022 à 15:03:01

Alors, pourquoi les allocations dynamiques. Vaste question.

Pour moi, tout commence le 27 aout 1527. Je bourlinguais pas mal du côté des plaines désertiques du nord de l’Espagne. Il faisait chaud et sec, je transpirais comme un âne dans mon armure. Ca faisait des jours que mon canasson n’avait pas bu d’eau et cela se voyant dans sa démarche. Je redoutais le moment où il allait rendre son dernier souffle et qu’il me faudrait continuer a pied. Mais rien ne pourra me détourner de ma mission.

Quatre jours plus tard, allégé de ma monture, de ma cuirasse et de l’ensemble de mes bagages, je continuais tant bien que mal à avancer à pied. Le soleil avait eu raison des couches superficielles de ma peau, qui tombait en lambeau. Mais la douleur de ces brulures sur l’ensemble de mon corps me permettait de rester conscient. Tout au moins suffisamment conscient pour continuer à avancer.

C’est au début de l’automne 1734 que j’atteint enfin le premier village. Je pus me désaltérer dans le puit au centre de la place. La première gorgé d’eau depuis que j’avais commencé mon périple fut douloureuse, tellement ma gorge était sèche. Les villageois, craintifs dans un premier temps, devinrent plus accueillant quand ils comprirent qu’ils n’était pas l’objectif de ma quête et de mon courroux. Mais je ne pouvais pas m'attarder. Il me fallait continuer encore et encore.

Quel rapport avec l'allocation dynamique me demanderez vous ? Soyez patient, cela viendra en temps et en heure. Le voyage est long et plein d'enseignement. La suite nous emmènera dans les galères grecques en Méditerranée et au confins de forêt d'Amazonie. Et à la fin, le pourquoi de l'allocation dynamique deviendra plus clair.

-
Edité par gbdivers 8 juillet 2022 à 21:51:09

  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2022 à 15:08:42

J'ai été plus entrainé par ce récit épique que par la dernière saison de game of throne. Je ne veux pas la suite de ce périple, j'en ai BESOIN !
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2022 à 15:16:24

Ah ? Un besoin ? En termes marketing, ca veut dire des sous. Tu donnes combien ?
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2022 à 15:18:03

Quand même, de 1527 à 1734, ca fait une sacrée promenade...
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2022 à 15:19:20

Salut,

JadeSalina a écrit:

Je ne comprends pas pourquoi vous trouvez que c'est bien de faire des allocations dynamiques de partout.

Et c'est normal que tu ne le comprenne pas, vu que l'on considère au contraire, qu'il faut éviter "autant que possible" les allocations dynamique de mémoire :D

Je suis désolé de t'agresser dés la troisième ligne (j'aurais préféré attendre la vingtième ou la cinquantième, cela m'aurait sans doute moins fait passer pour un bourreau :D ), mais, encore une fois, tu viens avec des certitudes complètement fausses, et des prémisses foireuses, pour ne pas dire aberrantes ;)

Pour faire simple: on considère qu'il faut que trois conditions sine qua non soient remplies pour justifier le recours à l'allocation dynamique, à savoir:
  1. que l'objet que pour lequel on envisage d'y recourir fasse partie d'une hiérarchie de classes (tu sais, héritage, respect du LSP et tout ce qui s'en suit)
    1. que tu envisages de manipuler n'importe quelle instance d'une classe dérivée "comme s'il s'agissait d'une instance de la classe de base (LSP inside, de nouveau) et que
    2. le "type réel" de l'instance de l'objet soit défini à l'exécution

Si l'une de ces trois conditions n'est pas respectée -- parce que tu envisage de définir le type d'une seule instance d'une classe dérivée à la compilation, par exemple  ou parce que tu envisages de toutes facons de manipuler cette instance selon son type réel-- tu n'as aucune raison d'avoir recours à l'allocation dynamique.

JadeSalina a écrit:

Déjà c'est une opération qui peut échouer, donc avoir des opérations comme ça en plein milieu c'est dommage, et ensuite ça oblige à avoir un code dégueu car comme vous l'avez dit, "un code correct c'est un code avec un if par ligne".

Au moins, tu me donnes l'occasion de ne pas ** trop passer pour un bourreau, car, sur ce point, nous sommes d'accord (même si, te connaissant, je ne peux m'empêcher de penser que tu ne partage peut-être pas cet opinion, à cause du "comme vous l'avez dit" :'(:-°)

JadeSalina a écrit:

Du coup, fort heureusement, en C++ il existe des mécanismes pour éviter de rendre le code dégueu, comme le RAII, les conteneurs, les pointeurs intelligents, les exceptions. Les pointeurs intelligents et conteneurs, associés au RAII, permettent de ne pas s'occuper explicitement de la libération des ressources et les exceptions permettent d'éviter d'avoir un if par ligne, en permettant de faire remonter l'erreur à qui veut bien la gérer,

Non pas "à qui veut bien" gérer l'erreur... à qui "il y a du sens" à lui demander de la gérer (au moins partiellement)...

C'est à dire de la faire remonter jusqu'à l'endroit où il y a -- a priori -- suffisamment d'informations que pour réellement se donner une chance de corriger l'erreur.

Parce que la gestion d'erreur ne se limite pas à afficher dans la console ou dans un log quelconque "désolé, j'ai pas réussi à ouvrir un X, Y ou Z" ou "désolé, je n'ai pas pu obtenir la mémoire nécessaire pour représenter votre instance de la classe Abc".

Ce genre de chose ne fait que "laisser une trace", comme le chien qui pisse sur le trottoir pour "laisser son odeur" afin de savoir comment retourner chez lui. Cela ne nous sert qu'à nous permettre de retrouver (à peu près) l'endroit du code où le problème s'est produit.

JadeSalina a écrit:

bien que ça se discute, il vaut peut être mieux gérer les erreurs au plus proche de là où elles se produisent mais bon, ça permet aussi de gérer des erreurs qui se produisent dans les constructeurs,

Le fait est que "au plus près" ne veut certainement pas dire "n'importe où"...

S'il s'agit juste de "pisser sur le trottoir", fais le absolument où tu veux -- par exemple, au moment où tu constate le problème -- ce ne sera pas un problème.

Evites simplement de faire l'erreur de croire que, sous prétexte que tu as "pissé sur le trottoir", tu as résolu le problème ;)

Bien sur, il se peut qu'il n'y ait aucun endroit de ton code où il soit possible d'apporter ne serait-ce qu'une partie de la solution au problème, ce qui risque de faire carrément planté l'application.

Cependant, en tant que développeuse d'une fonction durant laquelle "il n'est pas impossible qu'un problème survienne", tu n'est absolument pas concernée par "ce qui se fait en amont" étant donné que tu n'a aucun moyen de le corriger.

Ce qu'il faut bien comprendre, c'est que le fait d'apporter une solution -- fut-elle partielle -- au problème rencontré ne pourra se faire que là où l'on dispose de suffisamment d'informations que pour essayer d'y apporter cette solution, et que ce sera toujours "au plus près du problème" qui ... remplisse la condition d'informations suffisantes

JadeSalina a écrit:

Donc tout ça pour dire que en fait, en C++ moderne, on fait toujours "n'importe quoi" comme en C, à savoir faire des allocations de partout etc,

Revoilà ta prémisses foireuse, que tu affiches comme une certitude inébranlable, alors qu'elle est totalement à coté de la plaque.

Car un (bon) développeur C++ ne fera pas "n'importe quoi", et surtout pas des allocations dynamiques partout et pour n'importe quelle raison.

Un (bon) développeur C++ sait (ou devrait savoir / apprendre) que l'allocation dynamique n'est que la toute dernière solution qu'il ne pourra envisager que si toutes les autres solutions s'avèrent être totalement irréalisables ;)

JadeSalina a écrit:

Mais le problème c'est que ça ne résout pas le problème initial !

Ben, le problème, c'est surtout ... qu'il n'y a pas de problème...

Enfin, si, il y en a un, vu que tu le ressent de cette manière.  Seulement, il est ... dans ta tête, dans ton incapacité ne serait-ce que imaginer que les gens puissent agir de manière différente que ce qu'un gourou (incompétent, pour ton malheur) t'a inculqué que les gens faisaient.

Or, le truc, c'est que les gens ne font pas ce que ton gourou prétend, et que, lorsque c'est effectivement le cas (car cela arrive), on fait tout pour les dissuader de continuer à agir de la sorte.

JadeSalina a écrit:

Autrement dit, le code C++ est clairement plus propre que la même chose écrite en C, mais au final il se passe la même chose en dessous,

Même pas, parce que ce que l'on essaye d'inculquer aux gens c'est:

  • de faire en sorte que le compilateur t'engueule lors de la compilation s'il se rend compte que ton code ne "respecte pas tous les prérequis" (static_assert)
  • de faire en sorte que ton programme plante en période de test si tu as une erreur de logique / de programmation (assertions)
  • de faire en sorte que le programme plante s'il y a une erreur avec les "données externes" qui n'a pas été gérée ou pour laquelle aucune solution viable n'a pu être fournie (exception)

Parce que l'on sait que, plus le temps va passer entre le moment où tu as écrit/modifié ton code, plus tu auras du mal à le corriger par la suite.

JadeSalina a écrit:

à savoir que les allocations peuvent foirer,

Remarques tu, à la lecture de cette intervention, comme tu peux rester bloquée sur un problème qui ne se pose finalement que de manière marginale?

Et c'est d'autant plus vrai que l'on essayera généralement de cantonner les allocations dynamiques  --  lorsque l'on n'a vraiment pas d'autre choix que d'y avoir recours -- à des endroits bien spécifiques.

JadeSalina a écrit:

Du coup c'est quoi qu'il fait ? Et bien allez regarder HH, c'est pas forcément un truc que je peux résumer en 2 phrases. Mais en gros pour la mémoire je l'ai déjà dit plein de fois, il a une ou plusieurs "zones mémoire" qu'il appelle des "memory arena" allouée à l'avance (ou qui peut grossir sur mesure au besoin), à partir de laquelle il peut récupérer de la mémoire sur commande en étant sûr qu'elle y est, donc ça fait que ce n'est pas la peine de vérifier les allocations (donc ça simplifie le code) et ça rend le programme plus robuste puisqu'il ne peut pas planter à cause de ça.

Et tu crois, sincèrement, que cela va empêcher le système d'exploitation -- qui est malgré tout le seul à pouvoir "autoriser" l'augmentation de la taille de ta "memory arena" -- de t'envoyer chier en te disant "désolé, tu en as déjà eu trop" ou "désolé, je n'en ai plus à te donner"???

Le principe dont il parle là, on le connait "par coeur".  La plupart d'entre nous l'ont utilisé et poussé dans ses derniers retranchements.  On ne dit pas que c'est "mauvais" ou "inutile", on dit que cela n'a de raison d'être, que ce n'est pratique, justifié, ou nécessaire que dans "certaines circonstances" et que ces circonstances ne vont être réellement représenter que 10% (et encore) des situations -- déjà rares -- dans lesquelles nous devrons avoir recours à l'allocation dynamique de la mémoire "par nous même" (comprends: à l'exception des classes de bibliothèques qui l'utilisent "par nature").

JadeSalina a écrit:

Et c'est là que je comprends pas votre approche moderne, ok vos programmes sont peut être écrits "proprement" et d'une manière lisible, etc, mais il peuvent toujours foirer au final, comme si on le faisait en vieux C. Alors que lui il fait en sorte que le programme ne _puisse pas_ planter en plein milieu. C'est quand même mieux non ?

Le gros problème, c'est que sur PC au moins (car c'est moins vrai sur console), son système n'empêchera absolument pas le programme de planter "en plein milieu" car, à moins de limiter arbitrairement et de manière stricte le nombre de chacune des données que ton programme manipule, ce qui posera problème dans de très nombreuses circonstances, il y aura toujours un moment où ton programme demandera "un espace mémoire" à l'arena et où, pour pouvoir répondre à cette demande, l'arena n'aura pas d'autre solution que d'augmenter sa taille, et donc, de s'adresser au système d'exploitation.

Et comme on court toujours le risque que le système d'exploitation "envoie chier" l'arena, le programme ne pourra pas faire autrement que de planter.

Il le fera peut-être "ailleurs" (du moins dans le code), il le fera peut être différemment, mais il plantera malgré tout quand même encore, et, potentiellement, au milieu de l'exécution "courante".

  • Partager sur Facebook
  • Partager sur Twitter
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
8 juillet 2022 à 17:53:40

@gbdivers:
La mémoire dynamique est le dromadaire que tu aurais dû utiliser au lieu d'un cheval ...
@JadeSalina:
Même en C, on peut faire de l'allocation dynamique proprement.
J'écris régulièrement du code dans lequel je ne fais qu'un seul malloc. Je donne le nom de la fonction appelante dans le perror()
  • Partager sur Facebook
  • Partager sur Twitter

Le Tout est souvent plus grand que la somme de ses parties.

8 juillet 2022 à 18:47:55

Tu ne m’avais pas du tout manqué
  • Partager sur Facebook
  • Partager sur Twitter

Si vous ne trouvez plus rien, cherchez autre chose.

8 juillet 2022 à 19:58:19

lmghs a écrit:

> Je ne comprends pas pourquoi vous trouvez que c'est bien de faire des allocations dynamiques de partout

Qui a dit une bêtise (je reste poli)  pareille? Moins il y a d'allocs mieux on se porte. Tu pars de mauvaises prémices.

Dans tous les programmes C++ que je vois, je remarque qu'il y a beaucoup de make_unique, des std::vector déclarés par ci par là, même dans le jeu d'échec de koala01, il y a une fonction qui renvoie la liste des mouvements autorisés, et bien devinez quoi, elle renvoie tout simplement une std::list, qui va donc faire des allocations dynamiques indépendantes à chaque appel alors qu'on aurait pu lui passer une arena et qu'elle choppe la mémoire à partir de là. Et apparemment ça a pas l'air de déranger grand monde de faire ce genre de choses, alors que justement c'est sous optimal, ça peut foirer, et ça peut fragmenter la mémoire à force d'allouer/désallouer n'importe où

Fvirtman a écrit:

Maintenant, d'une part en C++ moderne on évite les allocations à la main, mais on utilise des objets qui en font en interne (std::vector par exemple). Et oui, on a parfois besoin d'allouer un espace qu'on ne connaît pas à la compilation !

Oui le code est plus "propre" et clair en utilisant des conteneurs puisque c'est eux qui se chargent des allocations etc sans qu'on s'en occupe explicitement, mais même si les allocations sont invisibles pour le programmeur, le pauvre CPU va quand même les voir passer, c'est donc bien ce que j'ai dit, les containers de la STL et autres pointeurs intelligents ça revient juste à mettre du sucre sur un gateau empoisonné, alors qu'avec l'approche de Casey on essaye d'avoir un bon gateau dès le départ, au prix d'un code peut être moins lisible, avec des pointeurs nus par ci par là, mais avec un minimum de discipline on peut quand même avoir un code propre, mais l'avantage surtout c'est qu'on obtient un programme qui est plus optimal au niveau du code machine.

Fvirtman a écrit:

Mais ne pas faire d'allocation dynamique du tout, j'avoue que ça m'échappe. 

Le titre du sujet est peut être un peu putaclic, il y a quand même des allocations dynamiques avec la méthode Casey, puisque c'est comme ça qu'on obtient une grande quantité de mémoire. Après moi aussi il y a des choses qui m'échappent avec ce que dit Casey, certains trucs je les ait "compris" après quelques centaines d'épisodes, et d'autres je sais toujours pas de quoi il parle (des fois il dit juste qu'il trouve que tel truc est nul mais sans explications).

Fvirtman a écrit:

Je te prends un exemple simple : tu fais un programme qui demande combien tu as de personnes, puis pour chaque personne demande leur age, puis à la fin te redonne l'age de tout le monde. Toi et ton idole complexé vous faites comment sans allocation dynamique ? J'aimerais bien le savoir...

Comme je l'ai dit je comprends pas tout ce qu'il fait, c'est à lui qu'il faut demander comment il ferait tel ou tel truc, mais par exemple dans ce cas, pour éviter l'allocation dynamique même si on connait pas le nombre d'éléments à la compilation, on peut par exemple utiliser un tableau statique, si on connait le nombre maximal d'éléments. Tout ça pour dire que moi aussi je croyais que "c'était simple" à savoir que si on veut une zone mémoire pas connue à la compilation, "on a pas d'autre choix que de faire de l'allocation dynamique" et "heureusement qu'on a de beaux containers dans la STL pour gérer ça". La vérité c'est qu'on peut faire autrement, si on prend le temps de réfléchir à d'autres solutions, et pas juste se contenter du premier truc intuitif qui vient.

Deedolith a écrit:

Bon, je passe sur les details de la video , mais pour répondre à ta question: "Pourquoi faire des allocations dynamiques ?"

Simplement parceque la mémoire disponible sur le stack est minuscule (+/- 1Mo ?), et que l'espace disponible ne saurait suffire à n'importe quel programme un temps soit peut évolué (Hello World, ca ne compte pas, on est d'accord ?)

Sachant que la taille du stack est très limitée, comment vas-tu charger tes ressources qui font plusieurs dizaines voir centaines de Mo ?
Tu ne peux pas raisonnablement "reserver" un espace maximum, d'une part ce maximum est inconnu, d'autre part ca bouffe de la place inutilement (lire: c'est une gestion calamiteuse de la memoire).

Enfin, rend toi bien compte que Casey utilise les API Windows, et que même s'il n'écrit pas d'allocation dynamique de façon explicite, les API vont le faire à sa place.

Oui mon titre est un peu trop putaclic, je n'ai pas dit qu'on pouvait tout allouer sur la pile. Par contre quand vous dites qu'on ne peut pas réserver un espace maximum car ça bouffe de la place ? Là j'avoue ne pas comprendre... Si je ne sais pas de combien de mémoire j'aurais besoin, qu'est-ce qui m'empêche d'allouer dès le départ un bloc de 32 Go ou plus ? A partir duquel on pourra simplement extraire de la mémoire quand on en a besoin. Ou alors un bloc de 8 Go pour les projectiles, un bloc de 8 Go pour les ennemis, etc. Et en plus on sera sûr que la mémoire est contigue donc rapide à itérer sur les projectiles ou autre. Et contrairement à un std::vector il n'y aura pas d'invalidation des itérateurs.

koala01 a écrit:

Pour faire simple: on considère qu'il faut que trois conditions sine qua non soient remplies pour justifier le recours à l'allocation dynamique, à savoir:

  1. que l'objet que pour lequel on envisage d'y recourir fasse partie d'une hiérarchie de classes (tu sais, héritage, respect du LSP et tout ce qui s'en suit)
    1. que tu envisages de manipuler n'importe quelle instance d'une classe dérivée "comme s'il s'agissait d'une instance de la classe de base (LSP inside, de nouveau) et que
    2. le "type réel" de l'instance de l'objet soit défini à l'exécution

Si l'une de ces trois conditions n'est pas respectée -- parce que tu envisage de définir le type d'une seule instance d'une classe dérivée à la compilation, par exemple  ou parce que tu envisages de toutes facons de manipuler cette instance selon son type réel-- tu n'as aucune raison d'avoir recours à l'allocation dynamique.

Ceci nécessite effectivement de manipuler des pointeurs ou des références mais rien n'empêche de les récupérer depuis un bloc préalloué, si on a une fonction qui veut manipuler une classe virtuelle, elle a qu'à prendre un pointeur ou référence sur le type de base, donc on peut se passer de faire un make_unique ou autre. D'ailleurs par pitié, je vois des gens prendre des unique_ptr ou shared_ptr en paramètre tout ça car ils ont mis l'objet dans un unique_ptr/shared_ptr... NON ! La fonction s'en fout de savoir que ça vient d'un unique_ptr ou autre, elle veut juste une référence/pointeur sur le truc en question, sauf si elle veut participer au festival de l'ownership, mais sinon elle a pas à prendre en paramètre un pointeur intelligent.

Après il y a d'autres solutions que d'utiliser des fonctions virtuelles, par exemple un std::variant avec du CRTP, l'inconvénient par rapport aux fonctions virtuelles serait que du coup la taille du variant serait la taille du plus gros truc dedans, mais on s'évite le surcout de l'appel virtuel, donc ça peut se discuter. (et oui je suis pas contre utiliser un std::variant au lieu de faire une union typée à la main)

koala01 a écrit:

Et tu crois, sincèrement, que cela va empêcher le système d'exploitation -- qui est malgré tout le seul à pouvoir "autoriser" l'augmentation de la taille de ta "memory arena" -- de t'envoyer chier en te disant "désolé, tu en as déjà eu trop" ou "désolé, je n'en ai plus à te donner"???

Le principe dont il parle là, on le connait "par coeur".  La plupart d'entre nous l'ont utilisé et poussé dans ses derniers retranchements.  On ne dit pas que c'est "mauvais" ou "inutile", on dit que cela n'a de raison d'être, que ce n'est pratique, justifié, ou nécessaire que dans "certaines circonstances" et que ces circonstances ne vont être réellement représenter que 10% (et encore) des situations -- déjà rares -- dans lesquelles nous devrons avoir recours à l'allocation dynamique de la mémoire "par nous même" (comprends: à l'exception des classes de bibliothèques qui l'utilisent "par nature").

Oui ça peut toujours foirer avec l'arena si on a pas prévu une taille assez grosse, mais même dans ce cas on a pas besoin de planter, puisqu'on sait exactement où se trouve toute la mémoire qu'on a précédemment demandé, donc on peut par exemple enlever des trucs "secondaires" pour faire de la place pour des trucs importants, alors que si on utilise des containers STL ou autre et que ça foire, on peut pas faire grand chose, où va-t-on trouver de la mémoire à recycler ? Et attention je ne fais aucune différence entre un new/make_unique et utiliser des containers STL, pour moi ça ne change que la lisibilité du code, mais au niveau du code machine c'est pareil, à savoir qu'il y a des allocations.

PS : gbdivers vous êtes plus vieux que ce que je pensais

-
Edité par JadeSalina 12 août 2022 à 23:21:56

  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2022 à 20:27:00

Tu ne savais pas? gbdivers a connu l'Homme de Néandertal

  • Partager sur Facebook
  • Partager sur Twitter

Le Tout est souvent plus grand que la somme de ses parties.

8 juillet 2022 à 20:31:29

La légende dit même que je pourrais être l'homme de Néandertal.
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2022 à 20:48:42

gbdivers a écrit:

La légende dit même que je pourrais être l'homme de Néandertal.

Non, il a des cheveux sur ses représentations lui.
  • Partager sur Facebook
  • Partager sur Twitter

Mes articles | Nazara Engine | Discord NaN | Ma chaîne Twitch (programmation)

8 juillet 2022 à 21:41:34

Lynix a écrit:

gbdivers a écrit:

La légende dit même que je pourrais être l'homme de Néandertal.

Non, il a des cheveux sur ses représentations lui.


  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2022 à 0:50:15

C'est parce que gbdivers est vieux.
On a chassé le mammouth ensemble pendant 10 000 ans. :)

Ha oui, Néandertal faisait de l'allocation dynamique également.

Il faisait des realloc de sa grotte durant l'hiver et des free au printemps quand la glace fondait.

-
Edité par PierrotLeFou 9 juillet 2022 à 0:55:53

  • Partager sur Facebook
  • Partager sur Twitter

Le Tout est souvent plus grand que la somme de ses parties.

9 juillet 2022 à 0:52:13

Toujours avec des cas tout pétés @JadeSaline.

Penses-tu vraiment qu'on a attendu Casey pour faire des implémentations OS spécifiques avec des #define ? Vraiment ?

Tu fais encore des procès d'intention sans même regarder les implémentations "raisonnables".

L'homme de paille, ça va un temps mais faudrait pas le sortir tous les trolldi.

Il y a tellement de conneries dans tes explications que je pense que tu n'as pas fait un exercice basique en informatique système : faire un OS minimaliste. Sinon, tu te serais rendu compte des ignominies de ton discours sur le fonction d'une mémoire système.

  • Partager sur Facebook
  • Partager sur Twitter
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
9 juillet 2022 à 10:24:14

Salut,

JadeSalina, c'est un troll ou quoi ?

  • Partager sur Facebook
  • Partager sur Twitter

Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .

9 juillet 2022 à 11:06:08

gbdivers a écrit:

La légende dit même que je pourrais être l'homme de Néandertal.


Le chainon manquant !
  • Partager sur Facebook
  • Partager sur Twitter

Moderateur forum || FAQ 3D || discord 3D francophone || OC Tweak script

9 juillet 2022 à 11:06:54

bacelar a écrit:

Penses-tu vraiment qu'on a attendu Casey pour faire des implémentations OS spécifiques avec des #define ? Vraiment ?


Non je ne pense pas ça, tout le monde sait faire des #define comme ça, ce que je pense c'est que peut être que j'ai l'impression que vous avez tendance à implémentez le truc avec les "techniques de base" style PIMPL sans forcément réfléchir à une potentielle meilleure solution. Ok ça permet peut être de gagner du temps, et en plus ça sera pas forcément un problème à l'exécution, pour reprendre l'exemple des fichiers, c'est pas comme si on en manipulait des millions par seconde, donc je critique pas du tout cette solution. Ce que je critique plutôt, c'est le fait de toujours faire les choses d'une seule manière "connue" et "standard" au lieu de chercher à potentiellement trouver mieux et à faire avancer la connaissance.

Pour l'exemple des fichiers, oui j'ai vu ça chez Casey mais ce n'est pas le seul, par exemple eux aussi font comme ça : https://ourmachinery.com//apidoc/foundation/os.h.html#structtm_file_o (en plus ils ont aussi rajouté un champ "valid", je vous avais aussi parlé de cette technique de mettre un bool sur le handle pour simplifier la gestion des erreurs et sans utiliser d'exceptions)

Du coup vous allez aussi trouver que tout ceux qui font comme ça sont des idiots ou des trolls ? Comme je l'ai dit on retrouve certains trucs de Casey dans d'autres projets, comme par exemple dans cette petite startup qui s'appelle "Google" il me semble : https://developers.google.com/protocol-buffers/docs/reference/arenas 

-
Edité par JadeSalina 12 août 2022 à 23:22:39

  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2022 à 12:44:20

C'est quand même un cycle qui se répète:

1) JadeSalina voit une personne qu'elle apprécie et qui semble (de ses propres) dire faire mieux que tous les autres développeurs existants (comme détaillé lors du dernier post, JS est sensible à ce genre de discours).

2) Cette personne utilise une certaine technique dans un certain contexte.

3) JadeSalina se demande pourquoi on utiliserait pas cette technique dans tous les contextes.

4) Elle va demander sur OC, en partant du postulat que la raison pour laquelle on ne fait pas ça tout le temps, c'est parce qu'on est des gros nuls (ben oui, Casey a dit qu'il était meilleur que tous les autres, donc on ne peut être que des gros nuls).

5) On lui explique, pour certains avec une patience que je ne comprends pas, pourquoi son point de vue est erroné et pourquoi certaines techniques spécifiques ne valent pas dans le cas général (et pourquoi les solutions générales bien que pas optimales, sont un excellent point de départ dans la majorité des cas - rappel qu'un programme effectuant une tâche en 5ms et développé en deux semaines vaut mieux qu'un programme effectuant la même tâche en 4ms mais ayant été développé en deux mois).

6) JadeSalina, n'ayant aucune expérience et étant sublimée par le discours de son gourou réfute, en se disant que la seule raison pour laquelle on est pas d'accord c'est qu'on est trop fiers, qu'on ne connait pas la technique, ou simplement qu'on est trop nuls.

7) Le topic part en cacahuète parce qu'elle épuise la patience des participants.

8) On recommence.

On est sur un mélange de complotisme / dunning-kruger, il n'y a rien à faire à ce stade. La seule bonne raison de répondre et d'expliquer est d'éviter que d'autres personnes lisant ces messages ne tombent dans le même délire. Une alternative serait une action de la modération (bannissement / fermeture du moindre topic du genre).

  • Partager sur Facebook
  • Partager sur Twitter

Mes articles | Nazara Engine | Discord NaN | Ma chaîne Twitch (programmation)

9 juillet 2022 à 13:01:57

Lynix a écrit:

Une alternative serait une action de la modération (bannissement / fermeture du moindre topic du genre).

Deja demandé, ils peuvent pas.

Lynix a écrit:

La seule bonne raison de répondre et d'expliquer est d'éviter que d'autres personnes lisant ces messages ne tombent dans le même délire.

Oui, mais maintenant, les gens attendent avec impatience les conversation de JadeSalina, pour savoir la suite de mon histoire dans les galères grecques. D'autant plus que cette partie de l'histoire est passionante. Sans entrer trop dans les détails, cela implique des vendeurs de reliques anciennes, des chatons albinos, une caisse de brocoli et une romance. J'imagine que vous avez compris en partie avec ces indices ;) Bref, du roman épique !

-
Edité par gbdivers 9 juillet 2022 à 13:02:54

  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2022 à 15:12:21

JadeSalina a écrit:

Dans tous les programmes C++ que je vois, je remarque qu'il y a beaucoup de make_unique, des std::vector déclarés par ci par là, même dans le jeu d'échec de koala01, il y a une fonction qui renvoie la liste des mouvements autorisés, et bien devinez quoi, elle renvoie tout simplement une std::list, qui va donc faire des allocations dynamiques indépendantes à chaque appel alors qu'on aurait pu lui passer une arena et qu'elle choppe la mémoire à partir de là. Et apparemment ça a pas l'air de déranger grand monde de faire ce genre de choses,

Quand donc comprendras-tu que ce n'est que des détails d'implémentation?

Bien sur, ma fonction qui renvoie l'ensemble des mouvements autorisés renvoie une std::list.

Bien sur, cette liste va faire une allocation dynamique à chaque fois qu'on y rajoutera un mouvement possible.

Bien sur, nous aurions pu passer par une arena.

Et bien sur, cela ne dérange personne que ce soit effectivement le cas.

D'un autre coté, si on voulait passer par une arena, il serait facile de changer le code pour que cela se fasse, vu qu'il suffit de changer le std::default_alocator par un ArenaAlloctaor perso, et qu'un simple alias de type permettrait de généraliser son utilisation.

Mais, crois tu vraiment que cela aurait changé quelque chose? Crois tu vraiment qu'il n'y a "aucun risque" de voir notre arena dans l'obligation d'augmenter sa taille lorsque l'on veut simplement ajouter un mouvement dans cette liste?

Et, selon toi, que se passe-t-il si le système d'exploitation envoie chier ton arena pour une raison qui lui est propre?  Hé bien, c'est simple : l'allocation pour l'arena "foire", du coup ton arena ne peut pas donner la mémoire demandée par la liste et ... ton application plante.

Alors, dis moi: quelle est la différence entre ton arena et mon std::allocator, hormis l'endroit du code où le problème se situe?

JadeSalina a écrit:

alors que justement c'est sous optimal, ça peut foirer, et ça peut fragmenter la mémoire à force d'allouer/désallouer n'importe où

On sait bien que std::llist est sous-optimal du point de vue de l'accès aux éléments. Enfin, pour une certaine forme d'accès (l'accès aléatoire) aux éléments.

Crois tu que je n'y aie vraiment pas pensé au moment de choisir une std::list au lieu d'un std::vector?

Si j'ai choisi std::list, ce n'est pas sans raisons!

D'abord, parce que, ce que je voulais, c'est que l'ajout d'un déplacement potentiel se fasse en temps constant.

Bien sur, j'aurais pu utiliser un std::vector dont j'aurais contraint la capacité au nombre de déplacements potentiels, au demeurant facile à calculer (si toutes les pièces de l'échiquier sont présentes et que tous les déplacements de toutes les pièces sont valides et possibles, il y a au final 132 déplacements potentiels maximum).

Seulement, cela ne m'intéressait pas de travailler de la sorte parce que je savais que je n'atteindrai pour ainsi dire jamais cette valeur en cours de partie. Cela ne m'intéressait donc pas de "gaspiller" l'espace nécessaire pour représenter un si grand nombre de déplacements alors que je n'allais sans doute "le plus souvent" n'en mettre que "quelque uns".

Ensuite, parce que je me foutais pas mal d'avoir l'accès aléatoire aux éléments (dont std::vector m'aurait permis de profiter), pour la simple raison que je savais que je devrais de toutes manières parcourir l'ensemble des déplacements potentiels trouvés par cette fonction.

J'ai donc choisi l'alternative qui demande "un peu plus de mémoire" par élément (la std::list) mais qui me permettait en revanche d'assurer l'insertion en temps constant et de n'utiliser que la mémoire strictement nécessaire à la représentation du nombre réel d'éléments que j'allais ajouter.

Tu poses cependant le doigt sur un problème potentiel important: la fragmentation de la mémoire.  Crois tu vraiment que je n'y avais pas pensé au moment même?

Ce qu'il faut savoir, c'est que ce qui provoquera a priori le plus de fragmentation mémoire, ce n'est pas de remplir ta liste à proprement parler.

Parce que le système d'exploitation va -- a priori -- te donner pour chaque élément le premier espace mémoire disponible suffisant pour le maintenir en mémoire qu'il trouve.

Or, ce premier espace mémoire, on peut raisonnablement espérer que ce sera ... l'espace mémoire qui commence à l'adresse qui suit directement l'espace mémoire qu'il "vient de te donner".

Ce qui va donc le plus fragmenter ta mémoire, c'est de faire des allocations qui n'auraient absolument rien à voir avec ta liste entre deux ajouts d'éléments dans cette liste.

C'est si tu décides, par exemple, de remplir trois listes les unes après les autres, car cela impliquerait deux éléments de listes qui n'ont rien à voir avec celle qui t'intéresse entre les deux éléments successifs d'une même liste.

Et ce genre de problème, l'arena ne pourrait de toutes façons pas le corriger.

Or, tous les ajouts de déplacements se font dans une fonction qui ne s'occupe que de cela et, qui plus est, qui ne fait rien d'autre, une fois qu'elle commence à  remplir la liste, que de rajouter éventuellement des déplacements dans la liste avant d'avoir fini.

Bien sur, je fais appel à deux fonctions "externes" dans cette boucle, à savoir ChessBoard.checkColor et PiecePathChecker.check. Si tu regarde bien le code de ces fonctions, tu remarqueras que ces fonctions n'utilisent absolument pas l'allocation dynamique, ce qui renforce très largement la possibilité que deux éléments consécutifs de la liste se trouvent effectivement à deux adresses successives en mémoire.

De plus, as-tu remarqué que cette liste, une fois remplie, n'est utilisée que pour parcourir les éléments qui la composent, pour savoir si l'un des déplacements que le joueur en cours peut effectuer risque de mettre le joueur adverse en échec?

As tu remarqué que la "durée de vie" de cette liste s'étend sur 8 lignes maximum, dont:

  • deux lignes relatives à un test de contenu
  • une ligne de retour "par défaut"
  • un boucle (quatre lignes en tout, quand même) contenant un test et un retour différent des deux autres

As tu remarqué que, une fois encore, la fonction qui utilise cette liste ne fait rien d'autre avant d'avoir fini son taf, n'appelle aucune fonction qui serait susceptible d'effectuer une allocation dynamique de la mémoire "en plus" de celle nécessaire à la liste?

 Crois tu vraiment que, même s'il devait y avoir fragmentation de la mémoire ici, cela changerait quoi que ce soit?

Crois tu que les quelques 0.0001 secondes éventuellement perdues suites à

JadeSalina a écrit:

Oui le code est plus "propre" et clair en utilisant des conteneurs puisque c'est eux qui se chargent des allocations etc sans qu'on s'en occupe explicitement, mais même si les allocations sont invisibles pour le programmeur, le pauvre CPU va quand même les voir passer,

Oui, bien sur que le CPU va voir passer la demande de mémoire dynamique avec nos container standards...

Mais, as tu conscience du fait que le CPU verra aussi passer la demande de mémoire à ton arena? Quelle différence cela fera-t-il?

As tu conscience du fait que le problème n'a absolument rien à voir avec le CPU, que le problème a à voir avec le système d'exploitation, qui, dans le cas d'une arena ou dans le cas d'une allocation "plus limitée" demandée par nos conteneurs standards aura toujours la prérogative de pouvoir la refuser s'il n'est pas en mesure de la satisfaire.

Je l'ai déjà dit, je le répète: l'arena ne fait que "déplacer la partie de code où problème se pose", elle ne change rien (du moins sur PC) au moment de l'exécution auquel le problème surviendra.

JadeSalina a écrit:

c'est donc bien ce que j'ai dit, les containers de la STL et autres pointeurs intelligents ça revient juste à mettre du sucre sur un gateau empoisonné,

Ce que tu n'arrives simplement pas à comprendre, c'est que ce n'est pas un problème de langage, c'est un problème systémique:

A partir du moment où tu travailles sur un système "aux ressources limitées" (l'ordinateur) et que tu décides de déléguer la gestion de ces ressources à un système particulier (le système d'exploitation) dont la prérogative est de refuser l'accès à la ressource demandée, quel que soit le langage ou le "sous système" que tu mette en place, tu devras toujours passer par le fait de "quémander" l'accès à tes ressources aux système d'exploitation.

Avec, quoi qu'il arrive, le risque potentiel que le système d'exploitation use de sa prérogative pour te refuser l'accès à cette ressource "indispensable" à ton programme.

Peu importe qu'il y ait une arena, une machine virtuelle, un interpréteur ou un ramasse-miettes entre ton programme et le système d'exploitation: si le système d'exploitation te refuse l'accès à la ressource, tu es dans la merde... Et ce, dans toutes les langues de l'arc-en-ciel.

Le gâteau est donc peut-être amère ou d'un gout qui te déplait, il n'en est empoisonné pour la cause, car la base est la même pour n'importe quel langage.

Au passage, savais tu que l'oxygène est nocif?  Cela ne nous empêche pourtant pas de le respirer à longeur de jours, de mois et d'années ;)

JadeSalina a écrit:

alors qu'avec l'approche de Casey on essaye d'avoir un bon gateau dès le départ, au prix d'un code peut être moins lisible,

Le gâteau semble peut être meilleur, mais comme la base est la même, le sucre que tu nous reproche de mettre dessus ne fait que se retrouver dedans...

Et surtout, le principal point sur lequel nous ne sommes décidément pas d'accord, c'est sur le fait que l'on puisse se permettre d'avoir un code "un code un peu moins lisible".

Cela peut ** éventuellement ** passer quand cela se limite à quelques lignes de code (et je dis bien ** quelques lignes **, c'est à dire pas plus de trois ou quatre) planquée au fin fond d'un chemin d'exécution qui n'est exécuté qu'un seule et une seule fois durant l'ensemble de l'exécution du programme, parce que, a priori, c'est le genre de code sur lequel nous ne reviendrons jamais.

Mais je l'ai déjà dit et répété: la première qualité d'un code, avant même de pouvoir compiler, est toujours d'être facilement lisible et compréhensible par le développeur qui devra le modifier /corriger/ améliorer / faire évoluer.

Savais tu qu'un développeur professionnel écrit, tous langages confondus, en moyenne ... entre 10 et 50 lignes de code qui finissent en production par jour?

Je te parle donc d'un type (ou une bonne femme, ne soyons pas sexiste :D) qui passe huit heures par jour, cinq jours sur sept, à développer une application!

Ce n'est pas (forcément) à cause des réunions à rallonges et à charnières inutiles

Ce n'est pas (forcément) parce qu'il passe plus de temps à discuter sur les forums ou à regarder youtube

Ce n'est pas non plus à cause du temps passé à la machine à café ou à attendre que la compilation se fasse.

C'est parce que le plus gros de son temps, il va le passer à lire, à analyser le code qu'il a devant lui et à réfléchir. A comment résoudre un problème auquel il est confronté.  A  comment remplir un besoin (souvent mal) exprimé.

C'est parce qu'il va régulièrement refactorer le code qu'il a devant les yeux.  C'est à dire qu'il va supprimer 50 lignes pour le remplacer par un bloc de dix lignes qui fait la même chose

Et c'est donc parce que, au final, il va peut-être passer seulement deux pourcent de son temps à écrire du code qui finira en production.

Si ton code n'est pas lisible à la base, il ne faudra pas s'étonner si cette proportion descent en dessous de ... 1 ligne par jour. Non pas à cause des éventuelles refactorisations, mais bien à cause du temps perdu à analyser et à comprendre le code.

Quand on pense que tout ce temps perdu aurait pu être utile à autre chose, c'est quand même dommage de le perdre, non? Tu n'écris pas du code lisible (ou "un peu moins lisible") pour toi, au moment où tu écris ton code.  Ni même pour les dix minutes qui suivent l'écriture du code.

Tu écris un code lisible pour te donner -- à toi et à ton équipe -- l'occasion de ne pas "perdre trop de temps" à essayer de le comprendre dans six mois.

JadeSalina a écrit:

mais avec un minimum de discipline on peut quand même avoir un code propre,

Ah, je m'y attendais... Voilà le grand retour de la "théorie de salons"! Tu sais, ce genre de théorie à laquelle il faut reconnaitre un certain bon sens, proposée dans le confort d'un salon (ou d'un bureau de directeur) par des gens qui n'ont aucune idée de la réalité du terrain?

Bien sur, "YAKA" faire peuve d'un peu de discipline.  Bien sur "YAKA" demander, en tant que gestionnaire, à son équipe de faire preuve de discipline.

Seulement, la différence entre la "théorie de salons" et la pratique, c'est qu'en théorie, ca marche...

Hé bien non, au risque de te décevoir, "YAPAKA" décider "pour soi-même" de faire preuve d'un peu de discipline.  Non, "YAPAKA" (encore moins) demander à son équipe de fair epreuve d'un peu de discipline à son équipe.  Ca ne marche pas comme cela...

Parce qu'il y aura toujours un moment où la "situation de terrain" fera en sorte que l'on a "autre chose en tête" que la "discipline dont on nous a demandé/ dont on a décidé de faire preuve".

JadeSalina a écrit:

mais l'avantage surtout c'est qu'on obtient un programme qui est plus optimal au niveau du code machine.

Ca, ca reste encore à prouver.

Surtout depuis le cycle d'évolution du C++ -- dans lequel nous sommes encore -- entamé avec C++11.  Car une grosse majorité des évolutions apportées ont, justement, cherché à permettre au compilateur de fournir un code machine "plus optimal" que ce à quoi on serait arrivé "par nous même".  Et surtout parce que ca marche étonamment bien...

JadeSalina a écrit:

pour éviter l'allocation dynamique même si on connait pas le nombre d'éléments à la compilation, on peut par exemple utiliser un tableau statique, si on connait le nombre maximal d'éléments.

Et justement, tu fais comment pour définir le nombre maximal d'éléments?

Reprenons l'exemple en question: tu dois développer un programme de sondage pour l'INSE. Tout ce qu'ils peuvent te dire au sujet du nombre de sondé, c'est qu'il varie (mettons) généralement entre 200 et 3 000, avec un écart type de l'ordre de 5 000.

Cela signifie, en gros, que les sondages dans lesquels il n'y aurait que 10 sondés peuvent arriver, mais qu'ils sont relativement rare, et que ceux qui sonderaient plus de 8 000 personnes en sonderaient "particulièrement beaucoup".

Ce que cela sous-entend surtout, c'est qu'il "n'est pas impossible" d'avoir un sondage pour lequel le nombre de sondés serait -- mettons -- de 10 000, voire même 100 000 (ou plus), même si le risque d'avoir de tels sondages est "particulièrement faible".

Mais toi, tu veux faire un tableau de taille fixe. Soit. Quel nombre maximal vas-tu utiliser comme taille pour ton tableau?

10 ? ca fera planter ton programme neuf fois sur dix (si pas 99 fois sur cent)

3000 (la "moyenne maximale")? Voilà qui semble raisonnable, à ceci près que:

  • ca fait quand même "beaucoup de place perdue pour rien" lorsque l'on croise un sondage de 10 personnes (on pourrait même monter à 100 personnes, le problème resterait le même ;) )
  • Le programme risque quand meme malgré tout de planter dans -- mettons -- 5 % des cas, quand le sondage s'adresse à plus de 3 000 personnes, ce qui est inacceptable

8 000 (total de l'écart type)? cette valeur n'est qu'une moyenne et risque  malgré tout de ne pas être suffisante pour certains sondages (mêmes'il sont "particulièrement rares")

10 000? 100 000? plus encore? (en fait nombre maximum de sondés observés sur l'ensemble des sondages)

Et qui te dit qu'il n'y aura pas un jour -- va savoir pourquoi ou comment cela pourrait arriver -- un sondage qui prendrait les réponse d'un nombre encore plus important de personnes que le sondage le plus gros jamais observé jusqu'à présent et sur laquelle tu as basé ta limite arbitraire?

JadeSalina a écrit:

Tout ça pour dire que moi aussi je croyais que "c'était simple" à savoir que si on veut une zone mémoire pas connue à la compilation, "on a pas d'autre choix que de faire de l'allocation dynamique" et "heureusement qu'on a de beaux containers dans la STL pour gérer ça". La vérité c'est qu'on peut faire autrement, si on prend le temps de réfléchir à d'autres solutions, et pas juste se contenter du premier truc intuitif qui vient.

Ben non, justement parce que le premier truc intuitif qui vient, si on refuse l'allocation dynamique pour les collections, nous mènera systématiquement à une situation dans laquelle

  • Soit "beaucoup trop" par rapport aux besoins réels (un tableau de 100 0000 éléments dans lequel on n'en placera que ... 10)
  • soit "juste trop peu", parce que l'on avait estimé le maximum "un peu trop court"
Et que si la correction des problèmes que cela engendre passe par la recompilation, ce n'est vraiment pas efficace.

Pour faire simple: les limites arbitraires (comprends: qui ne se base que sur des calculs imprécis et des évaluations "à la grosse louche" des besoins réels) ne sont jamaisune bonne chose en pratique.

JadeSalina a écrit:

Ceci nécessite effectivement de manipuler des pointeurs ou des références mais rien n'empêche de les récupérer depuis un bloc préalloué, si on a une fonction qui veut manipuler une classe virtuelle,

Non, non, non... les classes virtuelles sont encore un autre problème, prière de ne pas mélanger les torchons et les serviettes!

JadeSalina a écrit:

qu'est-ce qui m'empêche d'allouer dès le départ un bloc de 32 Go ou plus ? A partir duquel on pourra simplement extraire de la mémoire quand on en a besoin.

Ben, que penses tu du fait que ton programme n'est peut être (très certainement, devrais-je écrire) pas le seul à être exécuté sur ton ordinateur?

Or, on en revient toujours au même problème: un ordinateur est un système fini: il ne dispose que d'une certaine quantité de ressources, et il est "particuilèrement difficile" (car il faut l'éteindre, le débrancher du secteur et l'ouvrir) de rajouter des ressources.

Et que toute ressource "réservée pour le seul usage" de ton application deviendra -- forcément -- indisponible pour les autres programmes qui pourraient en avoir besoin.

Le problème est que tous les PC n'ont pas forcément les 256 Gb de ram qui permettraient à ton application qui en réserve à elle seule 32Gb de fonctionner tout en s'assurant d'avoir "un peu de rabe" pour les autres applications.  Surtout si, malheur, il devait y avoir une deuxième ou une troisième application utilisant le même principe.

Le problème est que certains PC n'ont d'ailleurs même pas encore 32Gb de RAM (celui que j'utilise ici n'en a que 16, dont trois réservé pour la mémoire graphique !!!)

Le problème est que tu ne peux pas te permettre de "foutre ton système à genoux" rien que ... pour assurer une quantité de mémoire "arbitraire" à ton application. Surtout si cette quantité est disproportionnnée par rapport à ses beosins réels.

JadeSalina a écrit:

Ou alors un bloc de 8 Go pour les projectiles, un bloc de 8 Go pour les ennemis, etc. Et en plus on sera sûr que la mémoire est contigue donc rapide à itérer sur les projectiles ou autre. Et contrairement à un std::vector il n'y aura pas d'invalidation des itérateurs.

Ben, peut-être parce que, dans mon cas personnel, le bloc pour les projectiles a encore une chance de passer, mais que le bloc pour les ennemi mettra mon système à genoux?

JadeSalina a écrit:

Et en plus on sera sûr que la mémoire est contigue donc rapide à itérer sur les projectiles ou autre.

Ce qui ne fait donc aucune différence par rapport à std::vector, dont c'est la caractéristique principale ;)

JadeSalina a écrit:

Et contrairement à un std::vector il n'y aura pas d'invalidation des itérateurs.

Ben, en fait, tout va dépendre de comment tu vas ton tableau, quelle que soit l'origine de la mémoire utilisée par celui-ci...

Car ce qui invalide les itérateur est le besoin de modifier (ce qui signifie généralement l'augmenter) la capacité de ton tableau. Si tu fais en sorte de n'avoir pas à modifier la capacité du tableau, tu n'auras aucune invalidation d'iterateurs ;)

Par contre, si tu dois faire varier la capacité de ton tableau à un moment ou à un autre, le fait de passer par une arena au lieu de s'adresser "directement" au système d'exploitation  ne changera rien, du simple fait de la logique qui devra être mise en oeuvre pour y arriver, à savoir:

1- demander "au système" (d'exploitation ou d'arena) un espace de mémoire "plus adapté" au nombre d'éléments que l'on s'attend à maintenir en mémoire

2- copier les éléments existant (en supprimant éventeullement ceux "à supprimer") de l'espace mémoire en cours d'utilisation vers ce nouvel espace mémoire

3- marquer le nouvel espace mémoire comme celui "à utiliser pour accéder aux éléments"

4- rendre "l'ancien" espace mémoire au "système" ( d'exploitation ou d'arena), pour qu'il sache qu'il peut le réutiliser.

Or, il se fait qu'il y a tout à fait moyen de... demander à la classe std::vector de réserver une capacité "clairement définie".

Au final, il faut bien te rendre compte que le système d'arena ne présente aucun avantage ** réel ** par rapport à notre système de "collections standards", que la très grosse majorité des avantages ** supposés ** (par toi) du système d'arena sont commun avec "notre système de colletions standards".

Il faut bien te rendre compte que, en pratique, ton système d'arena ne fera au final rien d'autre que ce que fait "notre système de collections standards", qu'il les fera simplement "à d'autres endroits du code", et pas forcément "à d'autres moments en cours d'exécution".

Il faut bien comprendre que, bien que le système d'arena soit utile dans certaines circonstances, il le sera dans certaines circonstances très particulières, que c'est un "tournevis storks", qui ne pourra pas être utilisé pour resserrer une vis nécessitant un tournevis plat ou crussiforme, et qui ne pourra surtout pas être utilisé comme marteau ou comme scie.

JadeSalina a écrit:

Ceci nécessite effectivement de manipuler des pointeurs ou des références mais rien n'empêche de les récupérer depuis un bloc préalloué

Et rien ne nous empêche de le faire, vu qu'il suffit de d'utiliser un ArenaAllocator quelconque. Cepenant, je te renvoie sur la partie de réponse précédente: Dans quel intérêt si l'on n'est pas spécifiquement dans une (des rares) situation(s) dans laquelle l'arena apporte effectivement un bénéfice prouvé au programme?

JadeSalina a écrit:

si on a une fonction qui veut manipuler une classe virtuelle,

Ah, non, par pitié, ne mélangeons pas les torchons et les serviettes!

J'ai parlé de "hiérarchies de classes" et de "fonctions(potentiellement) virtuelles". C'est totalement différent de la notion de classes virtuelles.

JadeSalina a écrit:

elle a qu'à prendre un pointeur ou référence sur le type de base, donc on peut se passer de faire un make_unique ou autre.

Et c'est ce sur quoi on insite à longueur de temps: make_unique ne sert qu'à s'assurer que la fonction qui a l'ownership dispose d'un "élément complet, valide et bien formé", et si on sait quel est le type réel de l'élément que l'on veut (créer et) manipuler, il est préférable de le créer sur la pile.

JadeSalina a écrit:

D'ailleurs par pitié, je vois des gens prendre des unique_ptr ou shared_ptr en paramètre tout ça car ils ont mis l'objet dans un unique_ptr/shared_ptr... NON ! La fonction s'en fout de savoir que ça vient d'un unique_ptr ou autre, elle veut juste une référence/pointeur sur le truc en question, sauf si elle veut participer au festival de l'ownership, mais sinon elle a pas à prendre en paramètre un pointeur intelligent.

Tu fais juste l'impasse sur le fait que, à chaque fois que les gens transmettent un std::unique_ptr ou un std::shared_ptr "qui n'a pas lieu d'être" à une fonction,on leur fait la remarque et on les incite à corriger leur code ;)

JadeSalina a écrit:

Après il y a d'autres solutions que d'utiliser des fonctions virtuelles, par exemple un std::variant avec du CRTP, l'inconvénient par rapport aux fonctions virtuelles serait que du coup la taille du variant serait la taille du plus gros truc dedans, mais on s'évite le surcout de l'appel virtuel, donc ça peut se discuter. (et oui je suis pas contre utiliser un std::variant au lieu de faire une union typée à la main)

Tu as tout à fait raison: CRTP est une possibilité absolument géniale (j'éprouve personnellement un peu plus de mal à l'idée d'utiliser std::variant, mais bon)...

Il faut juste que tu saches à la compilation quel sera le type réel de la donnée que tu vas manipuler.

Nous sommes même "un cran plus loin" des "théories de salons" parce que cette théorie marche en pratique.

Le seul problème, c'est qu'elle ne s'applique pas à tous les cas pratiques possibles et envisageable. C'est que dés qu'il sera question de déterminer le type réel d'une donnée à l'exécution, elle ne nous servira à rien.

On ne peut donc pas jouer à "YAKA / YAKAPA" avec cette théorie car il y a trop de cas dans lesquelles ... ca marche en théorie, mais pas en pratique :'(

JadeSalina a écrit:

Et sinon on peut penser "encore plus loin" et s'éviter des problèmes. Prenons un exemple simple, on veut une abstraction pour représenter des fichiers, vous allez me dire qu'il faut une classe abstraite puisque chaque OS a des fichiers différents, donc il faut passer par des pointeurs, du coup il faut faire une allocation dynamique, et du coup la solution "naïve" de C++ c'est de se retrouver à faire un PIMPL en utilisant des fonctions virtuelles avec un unique_ptr.

Ben, pour le coup, tu risques d'être surprise... je ne me suis "pas foulé", car j'ai demandé au compilateur faire travailler le préprocesseur sur un code aussi simple que

#include <iostream>

int main(){
    return 0;
}

Ce qui m'a généré un code de ... 32 137 lignes rien qu'avec les inclusions en cascade, et dans lequel je suis sur de trouver absolument tout ce qui pourrait être nécessaire, même de manière indirecte à l'utilisation des flux d'entrée et de sortie.

A partir de là, je me suis amusé à compter le nombre de fois où le mot clé virtual était présent.  Il apparait 127 fois sur l'ensemble du fichier.

Alors, cela peut parraitre beaucoup. je te propose cependant de mettre cela en perspective avec les différents faits suivants:

  • cela ne fait malgré tout que toutes les 237 lignes en moyenne (236.9685 pour être précis)
  • le mot clé override n'est jamais utilisé
  • Toutes les fonctions redéfinies dans des classes dérivées sont déclarées virtuelles
  • il est utilisé deux fois dans le cadre d'un héritage virtuel (pour basic_istream et basic_ostream)
  • Il apparait 24 fois dans le cadre de la (re)définition de la fonction what() et / ou du destructeur des classes héritées de ... std::exception
  • il apparait 5 fois dans la définiton de fonctions membre de la classe type_info (qui ne semblent jamais être redéfinies)
  • il apparait 1 fois dans la définition du destructeur de la classe nested_exception
  • il apparait 2 fois dans la déclaration de fonction de la classe  __forced_unwind (de CxxAbi), à mettre de toute évidence en relation avec la gestion des exceptions
  • il apparait 8 fois dans le cadre de la classe error_category (qui n'a rien à avoir avec la hiérachie de std::exception)

Tout cela pour dire que, sur les 127 fois où le mot clé apparait inclut l'ensemble des fonctions redéfinies dans les classes dérivées, le tier de ses apparaitions participe à la gestion d'erreur d'une manière ou d'une autre.

Pour le reste, on trouve:

  • principalement les classes dérivées de basic_streambuff (qui est une classe tempalte et qui utilise donc le CRTP), de locale::facet ou de ctype
  • les classes dérivée de basic_ios, uniquement pour le destructeur, et qui utilisent également le CRTP
  • deux héritages virtuels (pour basic_istream et basic_ostream)

Mais te rends tu compte de ce que cela signifie?

Hé bien, je vais te le dire moi: cela signifie que, à part pour se donner l'occasion de détruire un objet inutile, les cas où l'on fera effectivement appel à des fonctions virtuelles en tant que telles (comprends: où il faudra passer par la vtable pour déterminer quel comportement adapter en fonction du type réel de l'objet) sont relativement rares et se cantonnent aux comportements à adapter pour nous permettre -- je simplifie -- de travailler avec des caractères qui ne soient pas forcément issu de la table ASCII.

Cela signifie aussi que, le jour où les systèmes d'exploitation décideront une bonne fois pour toutes d'abandonner la table ASCII au profit de caractères dont la taille est "plus importante que 8 bits", toutes ces fonctions virtuelles vont "sauter les unes après les autres", car elles seront devenues inutiles.

Et le plus beau de l'histoire, c'est que C et C++ on déjà prévu la transition par la manière dont ils ont défini la taille des différents types entiers

Il est vrai que si tu te limites à l'ASCII ou à une forme quelconque d'UTF, tu pourais peut-être arriver à faire quelque chose de "plus spécifique" et de "plus optimisé" à tes besoins.

D'un autre coté, on peut raisonnablement se poser la question de l'intérêt de le faire. De la question du temps que tu gagnera effectivement (toute autre chose étant par ailleurs égale) par rapport à ces implémentations "standard".

Car, comprenons nous bien: si tous "retards pour lesquels tu ne pourra rien faire" (le fait d'attendre que le système "trouve un crénau" pour te permettre la lecture ou l'écriture, ou d'attendre -- avec les disques durs mécaniques -- que la tete de lecture / d'écriture se positionne "au bon endroit, etc) mis à part, tu charge ou tu enregistre ton fichier en 2.398 secondes avec ton système, alors que le système standard le fait en 2.45 secondes, crois tu vraiment que cela vaille la peine?

Crois tu seulement que l'utilisateur de ton programme s'en rendra compte?

JadeSalina a écrit:

Oui ça peut toujours foirer avec l'arena si on a pas prévu une taille assez grosse,

Et c'est un problème récurrent auquel tu seras confronté avec ton arena :p

JadeSalina a écrit:

puisqu'on sait exactement où se trouve toute la mémoire qu'on a précédemment demandé,

Ben, a priori, nous aussi. j'ai un peu de mal à exprimer le pourquoi (ce qui, selon ma signature, implique que je ne le concois pas forcément bien, et je l'admet), mais, ne t'en fais pas, tu auras ton explication "tôt ou tard" ;)

JadeSalina a écrit:

donc on peut par exemple enlever des trucs "secondaires" pour faire de la place pour des trucs importants,

Ca, par contre, j'ai beaucoup plus de mal à comprendre comment tu comptes t'y prendre, que ce soit pour déterminer ce qui "est secondaire", ou pour arriver à libérer effectivement la mémoire de ces éléments.

Et, quand bien même tu y arriverais, cela ne résoudrait pas le problème de "la fragmentation" de la mémoire: rien ne te dis que la mémoire que tu auras libérée représente un espace mémoire suffisant à tes besoin ni qu'il se trouve "suffisamment proche" de l'espace mémoire sur lequel tu travaille que pour être utilisé

  • Partager sur Facebook
  • Partager sur Twitter
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
9 juillet 2022 à 19:50:21

koala01 a écrit:

J'ai donc choisi l'alternative qui demande "un peu plus de mémoire" par élément (la std::list) mais qui me permettait en revanche d'assurer l'insertion en temps constant et de n'utiliser que la mémoire strictement nécessaire à la représentation du nombre réel d'éléments que j'allais ajouter.

Si on veut absolument utiliser le moins de mémoire possible, effectivement la liste est probablement une bonne idée. Maintenant est-ce réellement un problème ? Puisque si il n'y a que 132 mouvements possibles, ça fait quand même pas beaucoup de mémoire à stocker de nos jours, en sachant que un Move c'est juste 2 size_t (d'ailleurs un size_t c'est pas un petit peu overkill pour stocker la position sur l'échiquier ?).

Donc si le but c'est d'utiliser le moins de mémoire possible, alors oui on peut se dire que c'est pas la peine de stocker 132 moves si on en utilise qu'une dizaine à tout casser, et donc la liste se justifie. Mais maginons qu'on commence à jouer puis que le joueur lance un autre logiciel qui prend toute la ram, du coup son pauvre jeu pourrait planter par manque de mémoire... Alors que si dès le départ on alloue les 132 mouvements, on est sûr que le jeu pourra tourner quoi qu'il arrive et qu'il ne plantera pas. D'ailleurs je ne vois pas de try/catch au moment où vous faites push_back, du coup vrai question que je me pose, et qui vaut pour tout le monde, comment vous feriez si le push_back lance une exception ? Comment votre programme pourrait se remettre d'un tel problème sans planter ?

Et si on veut par exemple faire une IA, elle a intérêt à être rapide, et pour moi c'est plus rapide de push_back dans un vecteur qui a déjà la capacité suffisante plutôt que de le réallouer à chaque fois, et en plus ça serait potentiellement plus rapide à itérer que la liste puisque on est sûr que les éléments sont à la suite en mémoire.

koala01 a écrit:

JadeSalina a écrit:

Ceci nécessite effectivement de manipuler des pointeurs ou des références mais rien n'empêche de les récupérer depuis un bloc préalloué, si on a une fonction qui veut manipuler une classe virtuelle,

Non, non, non... les classes virtuelles sont encore un autre problème, prière de ne pas mélanger les torchons et les serviettes!

Désolée j'emploie mes propres expressions... On parle bien de la même chose, quand je dit "classe virtuelle" je veux dire "classe qui contient des fonctions virtuelles", j'aurait dû dire "classe abstraite". Ce que vous vous appelez classe virtuelle moi j'appelle ça de l'héritage virtuel.

JadeSalina a écrit:

donc on peut par exemple enlever des trucs "secondaires" pour faire de la place pour des trucs importants,

Ca, par contre, j'ai beaucoup plus de mal à comprendre comment tu comptes t'y prendre, que ce soit pour déterminer ce qui "est secondaire", ou pour arriver à libérer effectivement la mémoire de ces éléments.

Pour déterminer ce qui est secondaire on peut par exemple se dire que c'est les trucs les plus anciens, ou les moins fréquemment utilisés, etc (https://fr.wikipedia.org/wiki/Algorithmes_de_remplacement_des_lignes_de_cache) et pour libérer la mémoire, au lieu de la libérer réellement on peut juste la réutiliser pour autre chose, par exemple au moment où on veut charger un truc, ça foire car il n'y a plus de mémoire du coup on vire un autre truc et on réutilise sa mémoire pour allouer ce qu'on voulait. Après oui si c'est pour allouer n'importe quelle taille à n'importe quel moment, on se retrouverait à réimplémenter malloc, ce n'est pas le but. On peut par exemple utiliser des arenas comme des pools, comme ça on sait que si on veut charger un objet, on peut trouver la place nécessaire en virant un autre objet du pool, et on est sûr que c'est la bonne taille puisque c'est un objet du même type

-
Edité par JadeSalina 12 août 2022 à 23:24:49

  • Partager sur Facebook
  • Partager sur Twitter
10 juillet 2022 à 11:06:46

JadeSalina a écrit:

Si on veut absolument utiliser le moins de mémoire possible, effectivement la liste est probablement une bonne idée. Maintenant est-ce réellement un problème ? Puisque si il n'y a que 132 mouvements possibles, ça fait quand même pas beaucoup de mémoire à stocker de nos jours, en sachant que un Move c'est juste 2 size_t

Non, mais bon, j'aime pas gaspiller inutilement. C'est un choix qui se vaut ;)

JadeSalina a écrit:

(d'ailleurs un size_t c'est pas un petit peu overkill pour stocker la position sur l'échiquier ?).

En valeur absolue, très certainement, vu qu'il suffit de six bits pour pouvoir représenter les 64 positions possibles

D'ailleurs, j'aurais pu faire tenir l'ensemble des données relatives aux pièces sur deux chars sous une forme proche de

I  M     R        L
|  |  |--+--|  |--+--|
|  |  |  |  |  |  |  |
+--+--+--+--+--+--+--+
b7 b6 b5 b4 b3 b2 b1 b0

I = In Game (True / False)
M = Moved   (True / False)
R = Rows  [0-7[ ==> [A-Z[
L = lines [0-7[ ==> [1-8[

Pour ce qui est des informations de positions, ce qui m'aurait permis de représenter le mouvement sous la forme de

struct Move{
   unsigned char from;
   unsigned char to;
};
expect: 128 <from < 255
        192 < to < 255 

et de représenter les notions relatives aux pièces sous une forme proche de

Ki Q  R  Bi  Kn P  B  W
|  |  |  |  |  |  |  |
+--+--+--+--+--+--+--+
b7 b6 b5 b4 b3 b2 b1 b0

Ki = King
Q  = Queen
R  = Rooks
Bi = Bishop
Kn = Knigth
P  = Pawn
B  = Black
W  = White

expect: Piece = Ki XOR Q XOR R XOR B XOR KN XOR P
        Color = B XOR W XOR empty
        ValidPiece = Piece AND (B OR W)

J'aurais même pu représenter l'utilisation de toutes le cases (comprends: le fait qu'il y ait une pièce ou non sur une case donnée) avec sous la forme d'un entier 64 bits, il m'aurait alors juste fallu:

un entier 64 bits "global"

  • deux entiers 64 bits  pour la position des différentes pièces de chaque couleur
  • 32 entiers 64 bits pour représenter la position des différentes pièces sur l'échiquier
  • deux entiers 64 bits "de calcul"

L'un des deux derniers aurait pu me servir à représenter l'ensemble des positions valides (après déplacement) d'une pièce particulière (par exemple, le fou noir qui se trouve en C5), et il n'aurait fallu que quelques AND et quelques OR (et un peu de "tirage de fil") pour ne garder au final que les positions réellement accessibles par la pièce.

Cela aurait d'ailleurs pu être vachement marrant de travailler de la sorte. Mais cela aurait rendu la logique inutilement plus complexe.  Or, tu l'as dit toi-même, il est déjà suffisamment difficile de s'y retrouver ;)

Par contre, le fait d'utiliser deux size_t n'est absolument pas overkill du point de vue hardware. 

Parce que la taille (en bits) de size_t correspond justement à la taille du type d'entier non signé qui permet de remplir un registre, et que même en utilisant un char, il faudrait de toutes façons à chaque fois remplir le registre.

Enfin, ce n'est certainement pas overkill du point de vue sémantique. Parce que, comme le nom l'indique si bien, size_t est un Size Type.  Autrement dit, un type destiné à représenter les tailles.

N'importe quelle taille susceptible d'être comprise par le processeur sur lequel on va travailler. Le fait que la valeur maximale susceptible d'être représentée est (très largement, il faut bien l'avouer) supérieure à la valeur maximale dont je vais avoir besoin n'a aucune espèce d'importance, du moment que je saches que je pourrai la représenter sans le moindre problème ;)

En outre, comme je viens de le dire un peu plus haut, la taille d'un size_t rentre exactement dans celle des registres.

Enfin, les deux char n'auraient malgré tout pas permis d'économiser grand chose, car je n'ai pas parlé des problèmes d'alignements en mémoire.  Mais ca, c'est une autre histoire ;)

JadeSalina a écrit:

Et si on veut par exemple faire une IA, elle a intérêt à être rapide, et pour moi c'est plus rapide de push_back dans un vecteur qui a déjà la capacité suffisante plutôt que de le réallouer à chaque fois, et en plus ça serait potentiellement plus rapide à itérer que la liste puisque on est sûr que les éléments sont à la suite en mémoire.

Tu n'as décidément pas encore compris l'objectif réel de ce code... je le répète donc

JE M'EN FOUTAIS PAS MAL QUE CE SOIT RAPIDE

Ce que je voulais, c'était qu'il compile correctement (comprends: sans erreur ni avertissements), qu'il fonctionne, qu'il serve à illustrer ce que j'avais expliqué tout au long du bouquin et de support à toutes mes explications de phase de conception et qu'il ne bloque aucun aspect de l'évolutivité du programme.

Considère ce code comme un "premier jet" (ce qu'il est, d'ailleurs) si cela te fait plaisir, et n'hésite surtout pas à l'adapter selon tes propres idées.  C'est la raison de sa licence MIT,  qui implique que n'importe qui peut le reprendre,   l'utiliser et

JadeSalina a écrit:

Je pense qu'on peut allouer 32 Go sans pour autant que ça prenne le moindre octet en mémoire

Et tu te plantes royalement.

Parce que, quand ton application demande "une certaine quantité de mémoire" au système d'exploitation, ce dernier lui donne directement de la mémoire réelle, à l'instant où l'application la demande, parce que, du point de vue du système d'exploitation, si ton application demande de la mémoire, c'est forcément pour l'utiliser.

Du moins, sous réserve

  • de trouver un espace de mémoire suffisant disponible (au besoin, en transférant la mémoire allouée à d'autres processus vers la mémoire virtuelle)
  • que la quantité de mémoire demandée ne dépasse pas la limite que le système d'exploitation accepte de fournir d'une seule fois (*)
  • que la quantité de mémoire totale demandée par l'application ne dépasse pas la limite que le système d'exploitation accepte de fournir à une application (*)

(*)Fut un temps où windows refusait systématiquement de fournir plus de 1Gb de mémoire au total à une application.  Le pire étant que, selon la version de windows (normale ou pro), cette limite était différente :p

Et dés que le système d'exploitation a fournit la quantité de mémoire demandée par ton application, il la considère comme "indisponible" pour le reste.

De plus, la mémoire virtuelle pose un tas de problèmes, entre autres, parce que cela signifie que les informations qu'elle contient ne sont plus présentens en RAM.  Et devine où les informations se trouvent?  Bingo: sur le disque dur (sur une partition "swap" sous linux, ou dans le fichier "pagefile.sys" /"swapfile.sys" sous windows).

En outre, le "rapatriement" de ces données va poser de sérieux problèmes, car, cela signifie que pour ne pas perdre les données qui se trouvent actuellement en RAM et qui seront remplacées par les données qui sont en mémoire virtuelle, ben, il faudra d'abord transférer les données qui sont en RAM sur le disque dur avant de pouvoir transférer celles qui sont sur le disque dur et qui nous intéressent vers la RAM.

Manque de bol, figure toi que le processus (du système d'exploitation) qui devra s'occuper de ces transferts, il a aussi besoin de mémoire réelle (non virtuelle) pour fonctionner, pour faire les traitements qui lui permettront de déterminer quelles données n'ont pas été utilisées depuis "suffisamment longtemps" que pour qu'il puisse se permettre de les transférer en mémoire virtuelle.

Si tu as un jour été confronté au fait -- tous OS confondus -- que même ta souris met "des plombes" à effectuer le moindre déplacement, et si tu te demandais pourquoi cela arrivait, voilà ta réponse: ton système était tellement surchargé par les différentes applications qui tournaient dessus, qu'il devait passer son temps à transférer des données de la mémoire réelle à la mémoire virtuelle et inversement.

Car oui, le système d'exploitation a lui aussi son système de mémoire virtuelle. Et quand deux systèmes de gestion de mémoire virtuelle (celui de ton application et celui du système d'exploitation) s'entrechoquent, ca fait bobo...

JadeSalina a écrit:

Non on pourrait allouer 50 blocs de 8 Go, un pour chaque liste d'objets qu'on va potentiellement utiliser, mais ça n'utilisera réellement que la mémoire qui est réellement utilisée

Même si tu dis explicitement au système d'exploitation que tu veux de la "mémoire virtuelle", pour que le système puisse te donner les 50 blocs de 8Gb, il n'y a rien à faire: il faudra bien qu'il trouve la place pour stocker toutes ces données. Que ce soit en RAM, sur le disque dur ou sur le cloud, s'il n'y a pas "quelque part" un "support physique" pour ces données, il va pas aller les accrocher au particules de pluie des cumulonimbus!

JadeSalina a écrit:

Comment ça vous aussi ? Par exemple pour la fonction qui renvoie la std::list, je vois pas comment vous pourriez faire en sorte qu'elle utilise la même mémoire d'un appel à l'autre. Sauf si vous prenez la peine de lui passer un allocateur custom, mais encore faut il s'en donner la peine, sinon vous ne pouvez pas savoir où se trouve la mémoire précédemment allouée

Je reformule donc:

Je ne sais pas exactement quelle est l'adresse effective de la mémoire que j'utilise, et cela m'indifère.  Par contre, je sais exactement quelle donnée utilise quelle adresse mémoire, vu que toutes mes données sont dans la pile d'appel.

Je sais donc que les premières adresses mémoire (et dont je me fous pas mal de l'endroit exact où elles se trouvent effectivement en mémoire)  qui seront libérées sont celles des dernière données (que j'utilise) à avoir été déclarées.

Et toi? à moins de maintenir "quelque part" (il faudra bien me dire où, au passage) un mapping complet de la mémoire de l'arena que tu fournis à tes donnée, ou pire, d'avoir une liste globale des données qui utilisent cette mémoire, comment vas tu faire pour savior que "tel bloc de données" (mettons: celui qui utilise la mémoire qui se trouve entre 0x12345 et x012468) est en fait utilisée par la variable C qui "n'a plus été utilisée depuis longtemps"?

Pire encore: Quand bien même tu arriverais à déterminer cela, quelle que soit la manière dont tu puisse t'y prendre (car il y a effectivement moyen :D ), quelle garantie as tu que la mémoire libérée par "l'éradication" de ta variable C suffira à  tes besoins?

Autrement dit qu'est ce qui te garanti:

  • soit que l'espace ainsi libéré sera suffisant pour contenir les données que tu souhaites y mettre ou, à défaut
  • que l'espace ainsi libéré sera contigu à "d'autres espaces disponible" dont la taille totale te permettra d'y faire rentrer les données que tu souhaites y mettre

De mon coté, cette garantie m'est donnée par le système d'exploitation, et, même sous windows, j'estime pouvoir lui faire confiance ;)

D'autant plus que j'ai bien conscience que, à moins d'y passer des mois et des années, personne ne sera capable seul d'obtenir un résultat ne serait-ce qu'à moitié aussi bon que celui du système d'exploitation.

JadeSalina a écrit:

Pour déterminer ce qui est secondaire on peut par exemple se dire que c'est les trucs les plus anciens, ou les moins fréquemment utilisés, etc (https://fr.wikipedia.org/wiki/Algorithmes_de_remplacement_des_lignes_de_cache)

Tu ne réponds pas à la question!

Bien sur que c'est facile au niveau du cache du processeur, vu que ce cache est divisé en pages. 

En simplifiant, on pourrait dire qu'un algorithme "raisonnable" consisterait -- tout simplement -- à commencer le rappatriement des données à "la première" page et à "tourner la page" à chaque fois que l'on a besoin de rappatrier de nouvelles données dans le cache.

Lorsque l'on a utilisé la dernière page, il suffit alors de retourner à la première, qui, forcément, contiendra les données les plus anciennes ... Emballé, c'est pesé.

Mais, au niveau de ton arena et de ton programme, cela se traduit comment?  Et surtout, quelles sont les garanties que tu as, en travaillant de la sorte, que l'espace mémoire libéré sera suffisant pour satisfaire tes besoins du moment? Quelles garanties as-tu en travaillant de la sorte, tu ne te retrouvera pas à "éradiquer" une donnée dont tu avais encore besoin, même si elle n'a pas été utilisée depuis longtemps?

  • Partager sur Facebook
  • Partager sur Twitter
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
10 juillet 2022 à 13:34:23

JadeSalina a écrit:

Fvirtman a écrit:

Je te prends un exemple simple : tu fais un programme qui demande combien tu as de personnes, puis pour chaque personne demande leur age, puis à la fin te redonne l'age de tout le monde. Toi et ton idole complexé vous faites comment sans allocation dynamique ? J'aimerais bien le savoir...

Comme je l'ai dit je comprends pas tout ce qu'il fait, c'est à lui qu'il faut demander comment il ferait tel ou tel truc, mais par exemple dans ce cas, pour éviter l'allocation dynamique même si on connait pas le nombre d'éléments à la compilation, on peut par exemple utiliser un tableau statique, si on connait le nombre maximal d'éléments. Tout ça pour dire que moi aussi je croyais que "c'était simple" à savoir que si on veut une zone mémoire pas connue à la compilation, "on a pas d'autre choix que de faire de l'allocation dynamique" et "heureusement qu'on a de beaux containers dans la STL pour gérer ça". La vérité c'est qu'on peut faire autrement, si on prend le temps de réfléchir à d'autres solutions, et pas juste se contenter du premier truc intuitif qui vient.


Je reste sur ma faim quand tu me dis que tu ne comprends pas ce qu'il fait et que c'est à lui qu'il faut demander !

Par contre, pour mon exemple simple, le coup du tableau statique (de quelle taille ? 1000 ? 5000 ?), même sans être sa sainteté Casey je dis que c'est dégueulasse. Car comme il a déjà été dit plus haut, la taille sur la pile est limitée, donc si tu mets des tableaux statiques de taille au hasard par ci par la, tu risques de faire sauter la mémoire, vraiment c'est pas une bonne idée.

Mais je reste sur ma faim sur la méthode du grand vizir Casey pour palier à ce petit problème sans allocation dynamique. Si tu en sais davantage dis moi.

  • Partager sur Facebook
  • Partager sur Twitter

Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

10 juillet 2022 à 14:05:06

koala01 a écrit:

Par contre, le fait d'utiliser deux size_t n'est absolument pas overkill du point de vue hardware. 

Parce que la taille (en bits) de size_t correspond justement à la taille du type d'entier non signé qui permet de remplir un registre, et que même en utilisant un char, il faudrait de toutes façons à chaque fois remplir le registre.

Enfin, ce n'est certainement pas overkill du point de vue sémantique. Parce que, comme le nom l'indique si bien, size_t est un Size Type.  Autrement dit, un type destiné à représenter les tailles.

Alors là je veux bien l'expertise de Helba à la rescousse si je dit une connerie. Déjà c'est overkill au niveau de la place que ça prend en mémoire, puisque vous êtes d'accord qu'un std::array<size_t, 32> prend plus de place qu'un std::array<char, 32>. Ensuite oui le CPU ne peut pas travailler directement sur des char, il va devoir le mettre dans un registre de 64 bits. Mais le pire c'est que ça va même pas se produire... Pourquoi ? Car le compilateur est (j'espère) pas trop mauvais, et va donc faire en sorte que les traitements exploitent les registres AVX, donc en gros il va charger le pauvre char dans un registre de 256 bits, mais au lieu d'en charger un seul et de gâcher 248 bits, il va en mettre non pas un mais 32 !! Ce qui fait que non seulement le registre est rempli de données "utiles" mais en plus le calcul se fera sur 32 char à la fois ! Alors qu'avec des size_t il ne pourrait en calculer que 4 à la fois, en "gâchant" 99% du size_t puisque on stocke que des petits nombres dedans.

koala01 a écrit:

JE M'EN FOUTAIS PAS MAL QUE CE SOIT RAPIDE

Ce que je voulais, c'était qu'il compile correctement (comprends: sans erreur ni avertissements), qu'il fonctionne, qu'il serve à illustrer ce que j'avais expliqué tout au long du bouquin et de support à toutes mes explications de phase de conception et qu'il ne bloque aucun aspect de l'évolutivité du programme.

C'est vrai que j'ai craché un peu trop vite sur le code parce que pour moi l'objectif c'était d'avoir un code minimal qui fasse tourner le jeu, et en plus qu'il soit + ou - optimal en temps d'exécution. Alors que vous vous vouliez montrer des techniques de conception avancées etc, qui sont peut être un peu lourdes pour un simple jeu d'échec, mais qui peuvent effectivement se révéler utiles dans un gros projet, donc de ce point de vue c'est réussi.

koala01 a écrit:

JadeSalina a écrit:

Je pense qu'on peut allouer 32 Go sans pour autant que ça prenne le moindre octet en mémoire

Et tu te plantes royalement.

Parce que, quand ton application demande "une certaine quantité de mémoire" au système d'exploitation, ce dernier lui donne directement de la mémoire réelle, à l'instant où l'application la demande, parce que, du point de vue du système d'exploitation, si ton application demande de la mémoire, c'est forcément pour l'utiliser.

Helba heeelp !! Il me semble que le système peut complètement nous réserver une énorme quantité de mémoire, genre 32 To (oui, To) mais ça veut pas dire qu'on utilise le moindre octet, donc dans le gestionnaire des tâches on verra pas toute cette quantité. Pourquoi on est sûr que l'OS peut nous trouver 32 To de mémoire contigue dans l'espace virtuel ? Car l'espace virtuel est tellement énorme (il fait 2^64 octets, bien qu'on ne puisse pas forcément l'utiliser en intégralité, dépendant des limites de l'OS) qu'il y a forcément 32 petits To contigus à un endroit.

Par contre on est complètement d'accord que si on passe par le C++ la mémoire est toute de suite utilisée, mais là je parle en utilisant directement l'OS, qui peut nous réserver de la mémoire sans pour autant qu'on y touche tout de suite (et donc qui sera pas utilisée par le processus)



dragonjoker a écrit:

Tu ne m’avais pas du tout manqué

Vous avez rempli les 3 carrés de C++ donc vous faites partie des "puissants" d'openclassroom, du coup je suis allé voir votre github et c'est super cool etc, mais il y a un truc qui me chiffonne, pourquoi vous mettez les virgules au début de la ligne ? Par exemple

toWait = getEngine()->getRenderTargetCache().render( device
			, info
			, *data->queue
			, { { *uploadResources.commands.semaphore
			, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT } } );

au lieu de 

toWait = getEngine()->getRenderTargetCache().render(
    device,
    info,
    *data->queue,
    {{
        *uploadResources.commands.semaphore,
        VK_PIPELINE_STAGE_VERTEX_INPUT_BIT 
    }});

Ou encore

return { view.name
	, viewId
	, view.layout
	, view.factors };

au lieu de 

return {
    view.name,
    viewId,
    view.layout,
    view.factors,
};

Lynix a écrit:

On est sur un mélange de complotisme / dunning-kruger, il n'y a rien à faire à ce stade. 

Je crois que vous êtes tous dans la zone du milieu, et vous croyez que je suis à gauche alors que en fait je suis à droite :)

-
Edité par JadeSalina 10 juillet 2022 à 15:27:06

  • Partager sur Facebook
  • Partager sur Twitter
10 juillet 2022 à 14:39:32

JadeSalina a écrit:

et vous croyez que je suis à gauche alors que en fait je suis à droite

C'est ce qu'une personne sur la gauche du graphique dirait...
  • Partager sur Facebook
  • Partager sur Twitter