Partage
  • Partager sur Facebook
  • Partager sur Twitter

Il ne faut pas écouter tout ce qu'on dit

    26 mai 2022 à 14:11:13

    >(Oui se baser sur windows.h est idiot à mon sens )

    Il critique ces débiles de Windows, mais il n'a pas encore mis à l'amande l'API toute pourrie Win32 (rétro compatibilité, quand tu nous tiens) ?

    Il fait tout "from scratch" mais les Dll d'API de l'OS, l'OS, le Kernel, les Drivers, ça compte pas dans le "from scratch" ???

    Il fait son propre allocateur, mais les implémentations dans le Dll d'API de malloc et consort, ça compte pas dans le "from scratch" ???

    Il a une position "idéologique" qui est basé sur de l'ignorance ( et pas que de son public, malheureusement).

    Mais c'est toujours enrichissant d'avoir des débats.

    Faut juste pas trop pousser avec l'appel à l'ignorance, et sur le fanboyisme.

    • Partager sur Facebook
    • Partager sur Twitter
    Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
      26 mai 2022 à 14:39:57

      JadeSalina a écrit:

      Peut être que si on prend juste un petit bout isolé tout simple, le compilateur optimise bien, mais si c'est en plein milieu d'un plus gros programme, il n'arrive plus à optimiser le même bout de code ?

      Avec des "si", on met Paris en bouteille!

      Le compilateur ne travaille qu'avec des "petits bouts de code isolés", pour reprendre tes termes, vu que son "unité de base" l'unité de compilation (le fichier .cpp).

      Quand il travaille sur le fichier A.cpp, tout ce qu'il connait du "reste du projet", c'est ce qu'on lui a donné avec les inclusions d'en-tête ou -- depuis 2020 -- l'import de modules, c'est à dire:

      • la signature des fonctions (classiques ou membres de classes)
      • le code binaire (en fait, assembleur) à générer pour les fonctions inline éventuelle
      • le contenu des structures, des classes et des unions
      • les constantes de compilations diverses (constantes séparées ou énumérations diverses)
      • les alias de de types

      ET C'EST TOUT

      L'outil qui devra s'intéresser au code binaire dans son ensemble, c'est l'éditeur de liens, qui a ses propres règles pour optimiser tout cela correctement au niveau du programme.

      JadeSalina a écrit:

      Et là vous allez me dire qu'il est idiot et qu'il comprend rien,

      Oui, parce que c'est le cas...

      JadeSalina a écrit:

      ici il dit que le C++ est nul comme d'hab, mais aussi que le C tout court est nul

      Et rien que cela, ca ne te fait pas déjà "tiquer"?

      Que quelqu'un puisse venir dire que les deux langages les plus anciens (ils on 40 et 50 ans, quand même) à être encore massivement utilisés de manière industrielle -- qui ne soient pas uniquement utilisés dans des "secteurs de niche" et qui sont à la base même de l'informatique telle qu'on la connait aujourd'hui -- sont "nuls", cela te semble "tout à fait  normal"?

      Mais, bons dieux, s'il ne veut pas utiliser ces langages, qu'il fasse du python, du java, du C# ou n'importe quel autre langage, ce n'est pas le choix qui manque! Ou qu'il aille planter des carottes!

      JadeSalina a écrit:

      et c'est ce que je me suis dit pendant DES MOIS après l'avoir découvert, je comprenais pas son raisonnement (du genre, pourquoi prendre un Foo* quand l'objectif est juste de lire la valeur, autant prendre un Foo const&).

      Ben tu aurais peut être mieux fait de continuer à le croire...

      JadeSalina a écrit:

      Et pourquoi il dit que les templates c'est nul ? Pourtant c'est cool pour écrire du code générique non ?

      Ce n'est pas simplement parce que "c'est cool" qu'il faut utiliser les template... C'est parce qu'il y a des circonstances dans lesquelles c'est simplement pratique et plus efficace à tous niveaux que toutes les autres solutions qui s'offrent à nous.

      JadeSalina a écrit:

      mais j'ai l'impression que lui va plus loin que ça, par exemple ici il dit que le C++ est nul comme d'hab, mais aussi que le C tout court est nul (https://guide.handmadehero.org/code/day170/#2518). Et il dit aussi que ça le fait saoule d'écrire du code comme Handmade Hero (genre dupliquer le code pour avoir deux version int/float, c'est bof on est d'accord là dessus), mais il préfère ça que d'utiliser les templates "because they're terrible"

      Tu as l'impression qu'il va plus loin.

      Et toute la nuance est dans ce terme.  Mais, ne crois tu pas qu'il serait utile de réfléchir pour déterminer si cette impression n'est pas simplement due à une incompétence crasse qui l'a poussé à trouver des chemins détournés?

      Crois tu vraiment que c'est ce genre de choses qui correspond à "aller plus loin"?

      Nous t'avons prouvé son incompétence par A + B (gbdivers vient encore de le faire) à de nombreuses reprises, et pourtant tu reste persuadée que nous sommes tous des incompétents et que lui seul sait comment programmer?

      Mais, attends... Il arrive quand meme un moment où il faut se remettre les idées en place, tu ne crois pas?

      s'est-t-il seulement une seule fois clairement sur les raisons qui font que, selon lui, "template are terrible"?

      Parce que si ce n'est qu'un avis subjectif de sa part, parce qu'il n'aime pas la syntaxe ou qu'il n'arrive pas trop à comprendre comment cela fonctionne, ne crois tu pas qu'il ferait peut être bien de le garder pour lui, ou de l'exprimer d'une manière qui puisse laisser le choix aux gens d'être en accord ... ou non avec lui?

      Comprenons nous bien: Comme la plupart des technique "plus ou moins avancées", les templates sont le siège du meilleur comme du pire, car il y a -- effectivement -- moyen de faire des choses tout bonnement abominables (il y a d'ailleurs eu une ou discussions ici même à ce sujet).

      Nous sommes bien d'accord sur ce point ;)

      Il est également hors de question de nier que l'utilisation des template peut -- dans certaines circonstance -- avoir des conséquences désastreuses en termes de temps de compilation.

      A coté de cela, entre la facilité que cela apporte -- ne serait-ce que  parce qu'on n'est pas obligé de passer par des void *  ou d'écrire deux fois un code strictement identique pour gérer les entiers et les réels -- et les optimisations que cela permet au compilateur de faire, ne crois tu pas que la "complexité ajoutée" au  code en vaut la peine?

      Savais tu, par exemple, que  le code qui ne correspond pas à la situation actuelle (parce que l'un des paramètres template ne correspond simplement pas) n'est, tout bonnement, même pas compilé?  Que ce code se transforme en ... strictement rien dans le code binaire généré?

      JadeSalina a écrit:

      Moi je penche pour la 2ème option, car ce n'est pas du tout le seul dire ce genre de truc,

      Mais que sais tu de ce type, à part ce qu'il a bien voulu dire sur lui (et qu'il serait hasardeux d'essayer de vérifier) et que ses compétences tant en C qu'en C++ semblent... limitées, pourrait on dire?

      En deux mots: absolument rien.  Evidemment, tu nous diras que tu ne sais absolument rien de gbdivers, de lynix, de lgmhs ou de moi-même non plus.

      Seulement, toi et nous pouvons discuter, opposer nos points de vues.  Casey, à part regarder sa video, tu ne peux pas lui poser une question franche ;)

      JadeSalina a écrit:

      par exemple il y a MetaDesk (https://dion.systems/metadesk) qui permet de faire de la génération automatique de code facilement.

      Première phrase du lien que tu présente ici, juste après le liens internes:

      Metadesk is an ergonomic parser library for a simple—yet versatile—plaintext language.

      Ouch, c'est une bibliothèque... D'accord... Elle est écrite en quoi? Chercons un peu... Tiens, dans getting started, on trouve le code

      #include "md.h"
      #include "md.c"
      
      int main(void)
      {
        // now you can call MD_* functions!
      }

      Ca ressemble quand même vachement à du C (assez ancien qui plus est), ca, non?

      Et puis, as tu fais remarqué le petit mot "parsing" qui traine au milieu de la phrase? Cela signifie surtout que cette bibliothèque sert à ... retrouver les différentes parties sémantique dans du code.

      Enfin bon... Mettons que cette bibliothèque puisse effectivement nous aider d'une manière quelconque.  Où peut on se la procurer?  Ah, ben, c'est moche, car il n'y a absolument aucun lien qui nous permette de le faire :-°

      JadeSalina a écrit:

      Sinon moi je préfère les choses simples plutôt que les trucs de C++ qui ajoutent trop de sémantique qui prend la tête

      As tu seulement compris ce qu'est sont les ordinateur ou les langages de programmation?

      Pour ta gouverne, un ordinateur n'est jamais qu'un ensemble de pièces qui permettent de manipuler du courant électrique de différentes manières, et parsemées de "points de contacts" où l'on peut tester si le courant passe ou non.

      C'est peut être honteusement simplifié, mais ce n'est que cela.  Et cela signifie qu'un ordinateur ne sait rien faire d'autre que de dire "le courant passe ici, c'est vrai"  ou "le courant ne passe pas ici, c'est faux" (ce qui est déjà une convention en soi).

      Si, pardon, les circuits qui le constituent lui permettent en gros trois choses:

      • de copier une information depuis la mémoire dans un des registres qu'il utilise pour manipuler l'information
      • d'effectuer différentes opérations (mathématiques et de comparaison) sur les données qu'il vient de placer dans ses registres
      • de copier une information depuis le registre où elle se trouve à une adresse mémoire donnée

      Autant dire qu'un ordinateur est l'idiot du village!  Le seul truc, c'est qu'il est ... idiot rapidement :p

      Quand aux langages de programmation, ce n'est jamais qu'un ensemble de conventions permettant aux humains de se faire comprendre par "quelque chose d'aussi bête qu'un ordinateur".

      Alors, forcément que la sémantique est présente dans n'importe quel langage de programmation, vu que c'est la base même sur laquelle s'appuie n'importe quelle convention.

      C'est, par exemple, grâce à la sémantique que tu peux demander à ton mari de te passer une cuillère sans risquer de te retrouver avec un couteau. Et c'est grâce à la sémantique encore que tu sais que pour avoir une cuiller, tu devra demander "a spoon" à un anglais ou "een leepel" à un flamand!

      Certains langages (comme l'assembleur) apportent très peu de sémantique, d'autres en apportent (beaucoup) plus, c'est un fait.

      Mais poses toi la question de ce que ce "surplus de sémantique" apporte, sachant que tout langage de programmation va nous laisser apporter "notre propre lot de sémantique" afin de s'adapter à nos besoin, bon sang de bois!

      Imagine la différence entre deux ordres que je pourrais te donner:

      la version "simple", expurgée de toute "sémantique superflue" serait "Creuse un trou"  Du moment que tu connait la sémantique du mot "creuse" et celle du mot "trou", tu as normalement "tout ce qu'il faut  pour être contente", non?

      Maintnant, compare cette ordre avec la version "avec tout plein de sémantique superflue" serait "creuse un trou de 2 metres de cotés (ayant les coté perpandiculaires) et de trois mètres de profondeur".

      Quelle est, selon toi, la version de l'ordre qui me garantira -- sous réserve que l'ordre soit correctement exécutée -- le plus facilement d'obtenir exactement le trou dont j'ai besoin?

      Dis toi bien que l'ajout de sémantique ne fait que donner "d'avantage d'occasions" au "deuxième intervenant" dans la discussion (qui est, dans notre cas, la suite de compilation) de comprendre précisément ce que l'on attend de sa part.

      • 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
        26 mai 2022 à 14:40:05

        C'était quoi le sujet déjà?
        Ha oui! «Il ne faut pas écouter tout ce qu'on dit»
        • Partager sur Facebook
        • Partager sur Twitter

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

          27 mai 2022 à 16:14:15

          JadeSalina a écrit:

          Sinon moi je préfère les choses simples plutôt que les trucs de C++ qui ajoutent trop de sémantique qui prend la tête et d'autres personnes pensent pareil (https://pointersgonewild.com/2022/05/23/minimalism-in-programming-language-design/) (flute encore un article écrit juste au moment où j'évoque le sujet je vais finir par croire que mes pensées dirigent le monde). Et Rust on lui reproche de "saouler" car il est toujours en train de nous engueuler, ça rend la programmation pénible je trouve, il faut avoir une aide du compilateur ça je suis d'accord mais pas au prix de devoir penser à des millions de trucs etc, il faut que le langage soit simple pour nous autres humains.

          Justement l'erreur sur ce point, et j'ai l'impression que l'auteur du post que tu cites passe à côté aussi, c'est de croire qu'en ayant un langage qui ne nous force pas à penser à tous ces problèmes, les problèmes disparaissent. Ce n'est pas le cas. Si on prend un langage plus simple de conception comme C (et ici je vais volontairement ignorer que cette conception est bancale sur plein d'aspects, mais imaginons que C soit un langage simpliste et bien conçu), effectivement quand on écrit du code, rien ne va nous pousser à bien analyser tous les problèmes pour s'assurer qu'il n'apparaissent pas, et c'est bien le soucis. La conséquence de ça, c'est que lorsque des gens veulent s'assurer qu'un programme écrit en C ne produit pas d'erreur, ils doivent penser à tous ces problèmes, ils utilisent donc des outils qui sont capables (tant bien que mal) de pointer tous les potentiels problèmes qui peuvent apparaître dans le code. Sauf que comme le langage n'est pas pensé du tout pour ça à la base, c'est bien plus difficile et la charge mentale pour réussir ça est sans commune mesure avec la charge mentale apportée par un système de types un peu tatillon. C'est ça qui a mené à la création de langages comme Rust, l'envie d'être sûr d'écrire du code correct, en traitant les problèmes à la racine contre un peu de jus de cerveau, plutôt que "plus tard" quand c'est déjà hors de portée et ça réclame infiniment plus de jus de cerveau.

          C'est une énorme erreur de s'imaginer que l'absence d'outillage capables de pointer les potentiels problèmes dans le code les fait disparaître, ou même de minimiser l'importance de ces problèmes. Que des outils soient là pour les pointer ou non, les problèmes sont là, et ils ne vont pas disparaître parce qu'on a décidé de porter des œillères.

          -
          Edité par Ksass`Peuk 27 mai 2022 à 16:16:28

          • Partager sur Facebook
          • Partager sur Twitter

          Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

            27 mai 2022 à 21:17:30

            Deedolith a écrit:

            En lieu et place d'exemples choisis (car un exemple ne reflète pas la généralité), si tu nous montrait ce dont tu es capable via un défis sur un projet raisonnable (pas trop simple ni trop complexe) ?

            Par exemple:
            Fournir et implémenter une architecture permettant de réaliser les opérations suivantes:
            On désire importer dans une base de donnée quelconque, des données issues d'une source quelconque (fichier texte, CSV, Excel, Open Office calc, ou autre base de données)
            Les données sources devront être vérifiée,
            - Si toutes les règles de validation sont respectées, les données pourront être importées
            - Sinon, on désire obtenir un rapport d'erreur (fichier texte, csv, Excel, Open Office Calc ou base de données).

            NOTE: Il appartiendra aux codes utilisateurs de fournir les moyens de lecture / écriture des données.

            J'ai fait ça vite fait en restant dans le style de Casey : https://github.dev/jojolatina/etl 

            Il y a des importeurs qui prennent une source en entrée et les met sous une certaine forme sur laquelle on peut travailler, des transformeurs qui peuvent faire des modifications à ces données et des exporteurs qui exportent les données dans une destination. Tout n'est pas implémenté mais le "squelette" est là. Le code a été pensé en mode "bottom up" donc on part du simple et on rajoute au fur et à mesure, donc le code est pensé pour être évolutif, on peut rajouter des types de données, des formats, etc. L'utilisation est assez simple, on appelle juste les fonctions qu'on veut selon ce qu'on veut faire, et on peut faire nos propres fonctions pour des sources/destinations non supportées de base. On pourrait rendre l'utilisation plus simple pour l'utilisateur mais ce n'est pas le but en mode "bottom up", et on pourra toujours rajouter une couche d'abstraction par dessus par la suite. D'ailleurs il me semble que Lynix fait un peu comme ça au niveau des Nz::Material, à savoir que c'est lourd et verbeux à utiliser, mais au moins c'est flexible : les fonctionnalités "bas niveau" sont exposées à l'utilisateur ce qui est une bonne chose je trouve pour commencer. Après on peut mettre des couches d'abstraction par dessus pour faciliter l'utilisation mais pour moi il vaut mieux le faire après (il vaut mieux partir d'un bazar qu'on peut réordonner, plutôt qu'un truc "clean" qui n'est pas flexible et qui nous casse les pieds à faire évoluer).

            Dans le code il n'y a aucune "class" car Casey n'en met pas, mais pour moi ça serait pas un problème de les utiliser au lieu de faire des "struct + fonctions" (par exemple pour l'allocateur). Par contre, les "types de données", les "layout" etc, je préfère les laisser juste dans des struct car comme ça on fait ce qu'on veut avec, on a une flexibilité maximale, et rien n'empêche par la suite d'envelopper le "bas niveau" dans un truc plus simple/safe à utiliser. Alors que dans l'autre sens, on se retrouve bloqué car "on a mis telle fonction dans telle classe, ah oui mais il faut aussi lui passer tel truc, ah et ceci et cela" (on se bat contre l'architecture au lieu de la laisser émerger).

            Pour l'allocation dynamique, en général on pense tout de suite à mettre les trucs dans des std::vector, du genre ah on a plusieurs éléments par ligne, on ne sait pas combien, allez hop std::vector ! Que se passe t'il en faisant comme Casey ? Et bien le concept même de "tableau dynamique" devient superflu, car on en a absolument pas besoin ici, et ça veut pas dire qu'on fait "n'importe quoi" comme en vieux C avec des malloc/free partout. Il suffit de faire comme j'ai fait, c'est ce que Casey appelle des "memory arena" (https://guide.handmadehero.org/code/day468/#6310) (après on pourrait mettre du sucre de template par dessus, pour plus de confort, là je ne l'ai pas fait pour aller plus vite).

            Lynix a écrit:

            La dissonance cognitive a encore de beaux jours devant elle.

            Quelle dissonance cognitive ?


            HelbaSama a écrit:

            Je conçois aussi mon propre CPU : https://github.com/Kannagi/AltairX

            Ah oui vous faites des trucs super lourds, vous êtes comme Casey en fait, c'est intéressant tout ça :)

            Mais, bons dieux, s'il ne veut pas utiliser ces langages, qu'il fasse du python, du java, du C# ou n'importe quel autre langage, ce n'est pas le choix qui manque! Ou qu'il aille planter des carottes!

            Alors il critique le C/C++ parce qu'il trouve que c'est nul, mais pour lui ça reste quand même infiniment mieux que python ou java, sinon il l'utiliserait pas lui même. Et il va pas forcément planter des carottes, mais il a déjà proposé d'aller faire du shopping au lieu de faire des maths (https://guide.handmadehero.org/code/day364/#3689)

            Seulement, toi et nous pouvons discuter, opposer nos points de vues.  Casey, à part regarder sa video, tu ne peux pas lui poser une question franche ;)

            On peut lui poser des questions justement, c'est pour ça que je disais que vous aviez une chance immense que Handmade Hero soit toujours en cours, vous pouvez aller sur son live quand il fait la "Q&A" et il répond aux questions du tchat, c'est à la fin de chaque épisode, il pourra vous expliquer les trucs mieux que moi.

            -
            Edité par JadeSalina 27 mai 2022 à 23:18:01

            • Partager sur Facebook
            • Partager sur Twitter
              28 mai 2022 à 0:59:13

              @Deedolith

              C'est le problème avec ce genre de défi : la personne le fait et reste donc convaincu qu'elle a raison. Et on ne peut que reconnaítre que, effectivement, elle a fait le défi (ou aller dans la mauvaise foi, avec des "ouais mais...").

              Et surtout, cela ne montre rien du tout. Il n'a jamais été question de dire que le C est mauvais, que si on n'utilise pas des classes, des const, des pointeurs intelligents, etc. le code obtenu sera forcément bugé. Il existe des milliers de codes en C qui ne sont pas bugés (pas plus que les autres, on va dire)

              Et cela ne change rien à un fait : des débutants en programmation peuvent sortir un code non bugé lors d'un projet scolaire. Et pourtant, on a quand même des experts avec beaucoup d'expérience et des gros salaires qui font des bugs.

              Les problèmes de sémantiques, d'outils pour améliorer le code, toutes les techniques de conception moderne de logicielles, elles n'ont de sens que parce qu'on travaille sur des projets de plus en plus complexes, avec de plus en plus de lignes, avec des équipes de plus en plus grandes, sur du matériel de plus en plus complexe, etc. Des simples projets de tests ne permettent pas de comprendre ce genre de problématique.

              Le simple fait que Casey bosse tout seul, sur un projet pendant presque 8 ans, qui n'est toujours pas sorti et toujours pas testé, est suffisant comme argument pour dire que ce qu'il raconte n'est que du vent, tant que son projet n'a pas passé la validation du monde réel.

              (En plus, Jade a tendance a faire du cherry picking dans ses réponses, prendre les points qui l'intéressent, sans répondre aux arguments qui montrent que Casey et elle ont tort. Et a faire du mille-feuille argumentatif. En lançant un tel défi, ça lui donne un porte de sortie facile aux arguments qu'on a déjà donné).

              JadeSalina a écrit:

              c'est ce que Casey appelle des "memory arena"

              "memory pool", quand on sait de quoi on parle. https://en.wikipedia.org/wiki/Memory_pool (mais c'est du détail sémantique)

              Le problème est que tu t'arrange pour comprendre les choses (volontairement ou non) de la façon que cela t'arrange. On peut passer des heures à t'expliquer, si tu veux pas comprendre, tu ne comprendras pas. Donc t'embête pas à corriger ton code par rapport à ce que je vais écrire, c'est osef. Parce que oui, il est possible d'améliorer ton code pour prendre en compte mes points. Et cela ne change rien au probleme.

              Je mets de côté le fait que tu ne vérifie pas si ton malloc a réussi. Je n'ai pas lu en détail ton code et j'ai pas regardé les erreurs syntaxiques, c'est (presque) du détail.

              Ton code présente les mêmes problèmes que n'importe quel memory pool. Un memory pool, c'est juste un outil comme un autre, qui ne remplace pas toutes les méthodes de gestion de la mémoire.

              ----------------------------------------------

              Un premier point : la fragmentation de la mémoire. Imagine que tu définies un taille de bloc de 100 et que tu as un tableau qui fait 10 lignes de 51 elements. Avec ton code, un nouveau bloc est alloué dès que la taille demandée dépasse la taille du bloc. Donc pour une ligne de 51, tu vas utiliser 51, puis allouer un nouveau bloc pour la ligne suivante. 49 est perdu a chaque ligne. Résultat, ton code consomme 1000 (10*100) au lieu de 510 (51*10). Presque 50% de mémoire gâché, c'est pas du tout une structure de données efficace (si mal utilisée).

              ----------------------------------------------

              Tu pourras alors répondre : il suffit de donner un taille correct au bloc. Ce qui est vrai. Mais imagine si ton code est utilisé dans un vrai projet pro de plusieurs milliers de ligne et que la personne qui utilise ton code ne le connaît pas, il ne fait que l'utiliser. Et là, il voit que de la mémoire est perdue et il ne sait pas pourquoi.

              Tu attends de lui qu'il aille chercher dans les milliers de lignes de code, pour comprendre ce qui ne va pas et voir de lui même qu'il doit augmenter la taille des blocs ? C'est pas réaliste.

              (Ce problème n'est pas spécifique à ton code, mais toutes les structures opaques. Mais c'est un problème à prendre en compte quand on bosse sur des grosses architectures, alors que sur des petits projets comme le tien ou celui de Casey, ce genre de problème ne se pose pas trop)

              ----------------------------------------------

              Plus généralement, ta structure de données n'est pas du tout optimale. Elle l'est sur certain points (mémoire contigu), mais pas sur d'autres. Par exemple, le fait que tu dois conserver un champ de données pour le type. Et donc tu multiplie quasiment par 2 la consommation mémoire. Ce qui annulera en grande partie l'avantage d'avoir une mémoire contiguë (parce que plus grande conso mémoire = encore plus de cache miss).

              Bien sûr, une approche objet présentera la même problématique. Mais au final, ça veut dire que ton approche a un bénéfice limité. Tu as un code qui nécessite de l'allocation manuelle, mais qui ne résout aucun problème de fragmentation de la mémoire.

              ----------------------------------------------

              Et le dernier point (et a mon avis, le plus gros point), c'est qu'en réalité, ton code n'est pas de l'allocation dynamique. Pas dans le sens où tu peux allouer et désallouer aléatoirement des blocs de mémoire. Et cela te permet d'avoir (artificiellement) un code simpliste, qui n'est pas du tout représentatif d'un vrai code concret.

              J'explique : ton allocateur fonctionne comme un pile (LIFO). Tu charges les données, tu les mets les uns à la suite des autres, et tu fais l'inverse pour désallouer. Et en plus, tu as que des éléments de même taille dans tes tableaux (le type Data). Donc en réalité, ton code est même presque inutile : il serait possible, sur un cas d'utilisation aussi simpliste, de créer en fait un seul bloc mémoire (puisque la structure est de taille fixe).

              Par exemple, avec ton code, ca serait très compliqué de gérer des éléments de tailles très différentes, sans tout mettre dans une classe unique Data. Ta structure de données ne sait pas gérer cela et la corriger pour ce problème n'est pas du tout trivial.

              Idem si tu veux modifier dynamiquement ton tableau. Ton code n'est pas capable de gérer l'insertion ou la suppression d'éléments au milieu. Ajouter un élément nécessiterait de faire pleins de décalage, gérer les cas où un élément dépasserait du bloc courant après le décalage, etc. Et pour la suppression d'élément, ton code est encore plus mauvais : il ne gère pas du tout la désallocation d'une partie de la mémoire, il désalloue tout.

              Idem pour la gestion des erreurs : tu gères juste le cas où "fopen" échoue, donc tu as juste à gérer le cas en tout ou rien. Mais que se passe-t-il en cas d'erreur au milieu de la lecture (fichier sur une clé USB ou corrompu, fichier mal formaté, etc) par exemple ? Un tel code peut gérer sans problème une erreur avant les allocations (comme tu fais), mais ça devient très compliqué pour gérer des erreurs en pleins milieu, quand il y a déjà eu des allocations (en excluant la solution de simplicité de tout supprimer).

              Dit autrement, je n'ai jamais travaillé sur un projet pro qui aurait pu utiliser cette approche en remplacement de la POO.

              Cette approche est connue et utilisée depuis très longue, même par les devs C++ !!! Je crois même que les premiers exemples d'allocateurs personnalisées pour la STL que l'on trouvait dans les livres, c'était sur ce genre d'approche. Et c'est aussi la justification des flat containers et de l'utilisation de vector vs list. Bref, c'est bien connu des devs et si on utilise la POO, c'est en connaissance de cause, pas parce qu'on est trop con pour ne pas connaître ces approches.

              Ton approche de la programmation est juste trop naïve pour être réaliste. Ca marche dans projet simpliste comme celui la ou celui de Casey, mais pas sur des vrais projets. Tant que tu ne bosseras pas sur de vrais projets, comme l'a dit Helba, ce que tu racontes resteras juste du blabla naif.

              Un autre exemple de ta naïveté :

              JadeSalina a écrit:

              /* Ici on a un exemple très intéressant de comment
              gérer les erreurs d'une manière fiable, sans exceptions, 
              sans RAII et sans
              pour autant faire des goto dégueu à la C. */
              /* Tout simplement, les fonctions qui manipulent le layout 
              peuvent vérifier qu'il est valide et si ce n'est pas le cas,
              elles ne font rien */

              Écris maintenant le code pour ouvrir 10 fichiers et faire ensuite une tâche. S'il y a une erreur sur l'un des fichiers, il ne faut pas tenter de lire les fichiers suivants et désallouer correctement les fichiers déjà ouvert.

              Ta remarque est idiote, parce que basée sur un exemple simpliste (1 seule fichier). Mais dans un vrai code, on a pleins de ressources a gérer, que faire comme tu fais n'est pas scalable et que c'est justement pour ça qu'on a inventé le RAII et que l'on utilise les goto en C.

              JadeSalina a écrit:

              mais il a déjà proposé d'aller faire du shopping au lieu de faire des maths (https://guide.handmadehero.org/code/day364/#3689)

              Il ne sait pas résoudre un système de 2 équations du premier ordre !!?? Il fait une erreur et il n'arrive même pas à le voir. C'est du niveau lycée !

              Je comprend qu'il n'aime pas les maths, il est nul !

              JadeSalina a écrit:

              On peut lui poser des questions justement

              Oui, mais non. Quand on affirme des choses, on donne des arguments. On ne devrait pas être obligé d'aller lui poser des questions pour savoir pourquoi il affirme des choses.

              Et c'est pareil pour toi, quand tu viens ici nous sortir des "Casey a dit...", tant que tu n'apportes aucune explication, tu as 0 crédibilité à nos yeux. Pour ca qu'on te considère comme une fanboy sans cervelle. (Et non, donner un lien vers une vidéo où Casey affirme un truc sans argument, ca ne tient pas lieu d'explication).

              Reprend toutes les affirmations que tu nous a déjà donné dans cette discussion ou les précédentes et liste les arguments que tu as donné pour justifier ces affirmations (autre que "Casey a dit..."). Tu comprendras pourquoi on pense que tu dis que du vent.

              Et le peu d'arguments qui ont été donné et qu'on a pu vérifier, on a montré que c'était faux (ou au pire juste une opinion personnelle).

              -
              Edité par gbdivers 28 mai 2022 à 3:57:03

              • Partager sur Facebook
              • Partager sur Twitter
                28 mai 2022 à 7:46:16

                Ah oui vous faites des trucs super lourds, vous êtes comme Casey en fait, c'est intéressant tout ça :)

                C'est sensé être un compliment ,je suppose ?
                Et je fais aussi des trucs bien lourd que casey en terme de from scratch et JV
                (oui désolé mais j'ai l'impression que seul l'argument ad hominem , et avoir la plus grosse marche chez toi...)

                On peut lui poser des questions justement, c'est pour ça que je disais que vous aviez une chance immense que Handmade Hero soit toujours en cours

                Et pourquoi on aurait une "chance immense" ?
                Est ce que Casey pourrait m'apprendre un truc ?
                La réponse est non , et c'est pas une question de révélation divine là.
                En terme de bas niveau , je le bas de plate couture.
                En terme d’optimisation , pareil.
                En terme de dev de JV , c'est fait des choses bien plus intéressante que HH.
                En terme de from scratch  et en terme de code, j'ai fait mieux aussi et je le fais en C (tu pourra pas dire que je fais du C++ là )(si tu as vu mon github ,tu aura du code from scratch sur pas mal de mes projet perso).

                Mais je parle pas uniquement pour moi , mais c'est le cas de tout les intervenant ici , Casey ne fait rien de spécial , mais ça impressionne le/la débutant(e) en prog ,j'en doute pas.
                Et remercie encore gbdivers qui te fait une longue réponse pour contre argumenté ce que tu dis et ce que dit Casey.

                Et c'est pas de la mauvaise foi , ton truc d'allocation , c'est une technique connu.
                Donc non Casey n'a rien inventé, il redécouvre de vielle technique et l'explique avec aplomb que c'est comme ça qu'il faut dev ! :p

                -
                Edité par HelbaSama 28 mai 2022 à 8:33:27

                • Partager sur Facebook
                • Partager sur Twitter
                  28 mai 2022 à 11:59:01

                  Je mets de côté le fait que tu ne vérifie pas si ton malloc a réussi. Je n'ai pas lu en détail ton code et j'ai pas regardé les erreurs syntaxiques, c'est (presque) du détail.


                  Oui on pourrait mettre une vérification dans l'allocateur, et renvoyer un nullptr si ça a échoué, mais de toute façon si y'a plus de mémoire on peut pas faire grand chose, on pourrait mettre un exit + log directement dans l'allocateur, c'est pas comme si on pouvait faire quoi que ce soit sans mémoire. Vous faites bien de ne pas regarder en détail l'implémentation il y a sans doute des bugs, c'était juste pour donner l'idée.

                  Un premier point : la fragmentation de la mémoire

                   Oui si on alloue des blocs de quelques octets ça va faire plein d'allocations partout, mais le but c'est plutôt d'allouer de gros morceaux par gros morceaux. Dans l'exemple j'ai mis des blocs de 2Mo, qui correspond coïncidentalement  à la taille des pages larges en x86-64, donc on pourrait même directement allouer des pages virtuelles (du coup ça fait moins d'overhead par rapport à gérer plein de petites pages). Et en parlant de mémoire virtuelle, on pourrait aussi demander des blocs de 64Go voire plus, comme ça on est presque sûr que tout va rentrer dedans, et le mécanisme de mémoire virtuelle fera en sorte que la mémoire utilisée ne sera que celle réellement touchée (on a pas envie de bouffer 64Go de ram je vous l'accorde)

                  Et le dernier point (et a mon avis, le plus gros point), c'est qu'en réalité, ton code n'est pas de l'allocation dynamique. Pas dans le sens où tu peux allouer et désallouer aléatoirement des blocs de mémoire

                  J'espère bien que c'est pas de l'allocation dynamique ! On a pas envie de foirer des allocations en plein milieu (même si peut probable) ou que l'allocateur fasse une défragmentation à un moment random. Donc oui on ne peut que allouer/désallouer comme une pile, ce qui n'est pas forcément si déconnant que ça puisque comme ça chaque fonction alloue ce qu'elle veut, et ce qu'elle a alloué sera utilisé (donc on veut pas le désallouer). Par contre si on veut désallouer des morceaux qu'on utilise pour faire des calculs temporaires, on peut complètement le faire, il suffit de faire un "sub_alloc" qui sauvegarde l'état actuel de l'allocateur, ensuite on fait nos allocations, et ensuite on fait un "allocator_rollback" qui libère tout ce qui a été alloué depuis la sauvegarde (et si toutes les allocations rentraient dans le bloc courant, il suffit de faire "size = old_size", ce qui est plutôt rapide, et d'autres pourront utiliser la mémoire, ce n'est pas la peine de la "free", surtout dans un jeu où de la mémoire est allouée à chaque frame).

                  Et on est pas obligés d'avoir des éléments (type Data) de même taille, c'est peut être le cas ici, mais l'allocateur permet d'allouer n'importe quelle taille.

                  Idem si tu veux modifier dynamiquement ton tableau. Ton code n'est pas capable de gérer l'insertion ou la suppression d'éléments au milieu

                  Le but n'est pas d'insérer ou supprimer au milieu comme un tableau dynamique, si on veut faire ça on peut faire une liste chainée, et comme toutes les allocations se font à la suite contrairement à des malloc isolés, ça resterait globalement proche en mémoire, donc ça serait pas si problématique que ça

                  Idem pour la gestion des erreurs : tu gères juste le cas où "fopen" échoue, donc tu as juste à gérer le cas en tout ou rien. Mais que se passe-t-il en cas d'erreur au milieu de la lecture (fichier sur une clé USB ou corrompu, fichier mal formaté, etc) par exemple ?

                  Il se passerait la même chose, dès qu'une erreur est détectée, on l'indique, on remplit optionnellement le stream d'erreurs, et c'est tout non ? Y compris si on a déjà fait des allocations (il suffit de les "oublier" en faisant un restore)

                  Layout parse_file(char const* filename, Allocator* allocator)
                  {
                      Layout layout = {};
                  
                      /* On sauvegarde l'état de l'allocateur */
                      SubAlloc sub_alloc = allocator_save(allocator);
                  
                      FILE* file = fopen(filename, "rt");
                      if (!file)
                      {
                          layout.has_errors = true;
                          stream_append(&layout.errors, "Fichier invalide");
                      }
                      else
                      {
                          /* On parcourt le fichier */
                          for (...)
                          {
                              /* On fait plein de choses rigolotes, dont des allocations */
                              /* allocate(...) */
                  
                              if (...erreur dans le fichier...)
                              {
                                  layout.has_errors = true;
                                  stream_append(&layout.errors, "Expected ','")
                              }
                          }
                      }
                  
                      if (!layout.has_errors)
                      {
                          /* Pas d'erreur, on remplit officiellement le layout */
                          layout.type = LayoutType_Rows;
                          layout.data = rows;
                  
                          fclose(file);
                      } else {
                          /* On abandonne toutes les allocations temporaires */
                          allocator_rollback(&sub_alloc);
                      }
                  
                      return layout;
                  }

                  Ca veut dire quoi "en excluant la solution de simplicité de tout supprimer" ? On pourrait tout aussi bien faire des sub_alloc/rollback dans la boucle, pour pouvoir supprimer avec une granularité plus fine si on veut pas tout supprimer.

                  Écris maintenant le code pour ouvrir 10 fichiers et faire ensuite une tâche. S'il y a une erreur sur l'un des fichiers, il ne faut pas tenter de lire les fichiers suivants et désallouer correctement les fichiers déjà ouvert.

                  Je ne comprends pas la question, on peut très facilement ouvrir des fichiers, arrêter en cas d'erreur, et désallouer tous les fichiers à la fin, sans RAII ni goto :

                  struct File
                  {
                      /* ... */
                      bool has_errors;
                      Stream errors; /* Un moyen de faire remonter les erreurs, ça pourrait être autre chose qu'un stream */
                      void* platform_specific;
                  };
                  
                  /* Les implémentations se trouvent dans le code spécifique à chaque platforme */
                  File file_open(char const* filename);
                  void file_read(File* file, size_t offset, size_t size);
                  void file_close(File* file);
                  /**************************************************************************/
                  
                  void main()
                  {
                      File files[10] = {};
                      char const* filenames[10] = {...};
                      bool has_errors = false;
                  
                      for (size_t i = 0; i < std::size(files); ++i)
                      {
                          files[i] = file_open("");
                  
                          if (files[i].has_errors)
                          {
                              has_errors = true;
                              /* Inutile d'aller plus loin */
                              break;
                          }
                      }
                  
                      if (!has_errors)
                      {
                          /* Cool on peut faire nos traitements  */
                          for (auto& file : files)
                          {
                              /* ... */
                          }
                      }
                  
                      /* On ferme les fichiers */
                      for (auto& file : files)
                      {
                          /* Les files ne sont pas des FILE* débiles, la fonction file_close peut donc tout simplement ne rien faire
                          si le fichier n'a pas été ouvert */
                          file_close(file);
                      }
                  }

                  Et rien n'empêche que toutes les ressources, autre que des fichiers, soient gérées de cette manière.

                  D'ailleurs vous voulez "ne pas tenter de lire les fichiers suivants" en cas d'erreurs, mais quel est le but ? Ici il suffit de faire un break pour ne pas lire les suivants donc c'est pas un problème, mais que se passe t'il en cas de succès ? Et bien il faudra lire tous les fichiers... Donc est-ce si grave que ça d'ouvrir les autres fichiers alors qu'on sait qu'il y a une erreur ? Ce que je veux dire, c'est que si on est capables de lire tous les fichiers en cas de succès, on peut aussi les lire en cas d'échec, ça va pas couter plus cher, puisque on est censé pouvoir le faire en cas de succès. C'est juste que ça "gâche" un peu de CPU en cas d'échec puisqu'on pourrait éviter d'ouvrir les autres mais il faudrait le faire en cas de succès (qui peut le plus peut le moins).

                  Dans le code j'ai laissé une bizarrerie, c'est que toutes les fonctions d'importation, etc, ont la même signature. C'était dans l'idée d'avoir une interface unifiée, et que l'utilisateur puisse sélectionner des pointeurs de fonctions et nous les envoyer pour qu'on fasse le truc. Mais au final c'est lui qui appelle directement les fonctions donc ça n'a plus de sens de prendre un void*, on pourrait faire que chaque fonction prennent spécifiquement ce qu'elle veut, par exemple :

                  struct TxtTableDesc
                  {
                      char separator; /* le séparateur entre chaque colonne */
                      /* ... */
                  };
                  
                  Layout import_txt_table(char const* filename, TxtTableDesc* desc, Allocator* alloc);
                  Layout import_db_table(DbConnection* db, Allocator* alloc);
                  /* ... */

                  Ensuite sur le style j'ai fait comme Casey mais ça me dérangerait pas de prendre des const& au lieu de pointeurs, tout ça reste du détail, idem pour les allocations, c'est pourri de caster le truc à la main comme dans mon exemple, on pourrait utiliser des templates (Casey résout ce problème avec des macros)

                  Et sinon oui l'allocateur pile c'est un outil parmi d'autres, mais comme dit Casey dans des extraits que j'ai déjà donné, ça remplit 90% des cas. Une autre personne dit aussi qu'on connait la durée de vie et la taille (ou une borne supérieure) dans 95% des cas (https://www.gingerbill.org/article/2019/02/01/memory-allocation-strategies-001/)

                  -
                  Edité par JadeSalina 28 mai 2022 à 12:02:44

                  • Partager sur Facebook
                  • Partager sur Twitter
                    28 mai 2022 à 13:22:24

                    JadeSalina a écrit:

                    Quelle dissonance cognitive ?

                    La tienne.

                    Tu as envie de croire que Casey est le gourou qu'il faut suivre, et quand tu rencontres une opposition par rapport à cette croyance (avec des arguments), tu vas avoir plus facile à inventer une raison pour laquelle en fait tu as eu raison de croire ce que tu as cru jusqu'ici, plutôt que de te remettre en question. Notre cerveau a beaucoup de mal à accepter qu'il a été dans l'erreur, et préfère distordre la réalité (ou ne prendre que ce qui l'arrange) pour continuer d'évoluer dans sa petite bulle où en fait, il a raison. C'est humain, et tout le monde passe par là à un moment donné, ça demande des efforts de se remettre en question, et ceux qui sont soumis à la dissonance cognitive n'y arrivent tout simplement pas (c'est notamment l'un des principaux moteurs derrière le complotisme).

                    Je n'ai pas la foi de m'attarder sur toutes les conneries que tu as pu déballer (y compris ton code), d'autres le feront mieux que moi, mais je voudrais juste m'attarder sur ça, qui résume selon moi beaucoup de choses :

                    JadeSalina a écrit:

                    D'ailleurs vous voulez "ne pas tenter de lire les fichiers suivants" en cas d'erreurs, mais quel est le but ? Ici il suffit de faire un break pour ne pas lire les suivants donc c'est pas un problème, mais que se passe t'il en cas de succès ? Et bien il faudra lire tous les fichiers... Donc est-ce si grave que ça d'ouvrir les autres fichiers alors qu'on sait qu'il y a une erreur ? Ce que je veux dire, c'est que si on est capables de lire tous les fichiers en cas de succès, on peut aussi les lire en cas d'échec, ça va pas couter plus cher, puisque on est censé pouvoir le faire en cas de succès. C'est juste que ça "gâche" un peu de CPU en cas d'échec puisqu'on pourrait éviter d'ouvrir les autres mais il faudrait le faire en cas de succès (qui peut le plus peut le moins).

                    Pardon ?

                    Donc selon toi, c'est pas grave si un programme se rend compte très tôt qu'il y a eu une erreur, et décide de continuer son petit bonhomme de chemin - de façon totalement inutile, et c'est une donnée connue à ce stade - prenant plus de ressources, du temps utilisateur, etc. simplement pour programmer d'une façon plus proche de ton gourou ?

                    Suivant cette logique, il serait acceptable qu'un jeu charge tous ses assets pendant trois minutes, pour ensuite échouer en se disant "ah ben zut, le premier fichier que j'ai chargé - qui était crucial - n'a pas voulu, tant pis", plutôt que de reporter l'erreur le plus vite possible ?

                    C'est une farce à ce stade. En arriver à ce genre de raisonnement ça montre juste que la logique n'est plus là. Impossible de débattre avec quelqu'un qui te sort une connerie pareille.



                    • Partager sur Facebook
                    • Partager sur Twitter

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

                      28 mai 2022 à 13:56:45

                      JadeSalina a écrit:

                      Et sinon oui l'allocateur pile c'est un outil parmi d'autres, mais comme dit Casey dans des extraits que j'ai déjà donné, ça remplit 90% des cas. Une autre personne dit aussi qu'on connait la durée de vie et la taille (ou une borne supérieure) dans 95% des cas (https://www.gingerbill.org/article/2019/02/01/memory-allocation-strategies-001/)

                      Bon perso, j'ai la putain de grosse flemme de répondre à ton mille-feuille, c'est fallacieux donc c'est une perte de temps.

                      Je vais juste commenter cette énième citation que tu donnes. C'est déjà ce que j'ai fait pour pas mal de liens que tu as posté, mais là l'exemple est vraiment criant et il cristallise deux choses :

                      • le fait qu'en plus de pondre du mille-feuille argumentatif, tu cherry-pick bien ce que tu veux,
                      • le fait que quand tu lis un truc qui va dans ton sens tu ne réfléchis absolument pas à si ce qui est écrit est vrai.

                      Donc premièrement, pour le cherry-picking, dans l'article on trouve :

                      Please note that in domain specific areas, these percentages will be completely different.

                      Memory safety is another aspect to think about but I will not cover that in this series as that requires a separate set of solutions and trade-offs. In the domains that I deal with, memory safety is not a huge concern.

                      Donc déjà, contrairement à toi, l'auteur de ce post a la décence d'admettre que premièrement ce qu'il raconte ne s'applique pas à tout et que, deuxièmement, la question de la memory safety est volontairement mise de côté. Alors que bon, la memory safety, c'est un tout petit truc qui a été la cause de plus de 20% des vulnérabilité enregistrées (c'est probablement plus vu que toute ne sont pas tagguées correctement) par le NIST entre 2013 et 2017, cf. https://www.youtube.com/watch?v=dhoP-dyIr54 et la base de données fournies dans les liens.

                      Et ça fait bien le pont avec le deuxième point. Dans le post que tu cites, l'auteur nous dit "dans 95% des cas, on connaît le lifetime et la taille des données. Et à aucun putain de moment il nous dit d'où vient cette donnée, c'est du putain de doigt mouillé. Pour faire le parallèle avec la vidéo que je cite, on y trouve comment les données ont été acquise (en analysant une base de données des vulnérabilités) et le lien vers la source primaire des données : la base de données du NIST. Là le type est juste en mode "trust me bro, j'ai compté c'est ça".

                      Bref, tu sélectionnes ce qui va dans ton sens et tu crois bien ce que tu veux.

                      -
                      Edité par Ksass`Peuk 28 mai 2022 à 14:06:42

                      • Partager sur Facebook
                      • Partager sur Twitter

                      Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                        28 mai 2022 à 15:11:24

                        Donc selon toi, c'est pas grave si un programme se rend compte très tôt qu'il y a eu une erreur, et décide de continuer son petit bonhomme de chemin - de façon totalement inutile, et c'est une donnée connue à ce stade - prenant plus de ressources, du temps utilisateur, etc. simplement pour programmer d'une façon plus proche de ton gourou ?

                        Non c'est mieux de ne pas continuer dans de longues opérations si on se rend compte qu'il y a une erreur, je suis d'accord, c'est pour ça que j'ai mis un break dans la boucle d'ouverture des fichiers (à noter que du RAII aurait également appelé le destructeur des 10 fichiers si on avait directement return dans la boucle, comme ce qu'on fait nous juste après (il n'aurait pas juste appelé le destructeur des fichiers ouverts)). Ce que je voulais dire c'est que c'est n'est pas la mort si dans une situation donnée, on n'est pas optimal, du moment qu'on est efficace "sous la charge". Ici dans le cas des fichiers c'est peut être pas tout à fait pareil, mais par exemple imaginons qu'on ait des particules à mettre à jour. On va vouloir les mettre dans un tableau pour faciliter l'itération. Le problème serait que si des particules sont inactives, ce n'est pas la peine de passer dessus. Les mettre dans une liste serait une solution pour être sûr de n'itérer que sur les particules actives, mais serait lent à itérer. Du coup on va plutôt privilégier une solution qui va bien marcher "en charge, sous la pression" (donc les mettre dans un tableau et stocker par exemple un bool "is_active"), au détriment des ressources utilisées en "IDLE". C'est un compromis qu'on peut faire. Bien sur si on peut à la fois monter en charge efficacement tout en utilisant le moins de ressources quand il y a peu de travail, c'est mieux. Mais pour moi il vaut mieux privilégier les "peak" performances, mais c'est un choix à faire.

                        Le problème c'est que le C++ moderne n'encourage pas les performances, il propose une façon de programmer (exceptions, RAII, ...) qui peut difficilement atteindre le potentiel maximal. Par exemple, si vous utilisez des std::vector dans des fonctions, et bien le vector va lui même allouer de la mémoire, etc, sans se demander si on ne pouvait pas la récupérer d'une précédente allocation, ce qui est très facile à faire avec l'allocateur pile qu'on peut se faire passer à chaque fonction, et qui peut simplement allouer ce qu'elle veut et "rembobiner" ce qu'elle a alloué en faisant juste un "size = old_size" au moment de rollback (et éventuellement en libérant les blocs intermédiaires si la fonction a fait beaucoup d'allocations qui ne rentraient pas dans le bloc courant).

                        Ou alors comme je l'ai déjà dit, si vous oubliez un std::move vous allez "bloquer" des ressources qui ne seront pas libérées tant qu'on est pas sorti du scope du shared_ptr (alors qu'il aurait fallu le move), ou alors faire des copies inutiles

                        struct Info
                        {
                            std::string name;
                            /* ... */
                        };
                        
                        Truc foo()
                        {
                            Info info = {};
                            info.name = get_name(...);
                            /* info.  = ...; */
                        
                            return make_truc(info); /* dommage on a oublié de std::move */
                        }

                        Et la façon de Casey n'est peut être pas "catholique" pour du C++ moderne, mais c'est quand même une bonne idée, au prix de devoir faire des "allocate" et "rollback" à la main. Et on pourrait aussi faire en sorte que le rollback se fasse automatiquement, en mettant le code dans une macro dans le style de "defer" (je vous laisse chercher des implémentations C++), qui serait appelé en sortie de scope, si c'est ce qu'on veut absolument.

                        void faire_quelquechose(Allocator& allocator)
                        {
                            SubAlloc sub_alloc = allocator.save();
                        
                            defer
                            {
                                /* Ceci sera appelé en toute circonstances */
                                /* On pourrait aussi mettre des fermetures de fichier, ou autre */
                                allocator.restore(sub_alloc);
                            }
                        
                            /* On alloue tout ce qu'on veut sans se soucier de la libération */
                            auto* tmp_bytes = allocator.allocate<char*>(4096);
                        
                            /* On fait des traitements */
                            for (...)
                            {
                                /* Tiens on veut un peu de mémoire pour notre calcul temporaire, pas de problème ! */
                                auto* tmp = allocator.allocate<...>(...);
                        
                                if (...error...)
                                {
                                    log("erreur...");
                                    return;
                                }
                            }
                        }
                        

                        Et toutes ces allocations se feront vous savez quand ? JAMAIS puisque la mémoire a probablement déjà été demandée à l'OS depuis longtemps, soit à la frame précédente, soit au début du programme, ou autre. Donc c'est ce qu'il y a de plus performant qu'on puisse faire.

                        Et je veux complètement me remettre en question, c'est ce que je fais, mais ne vous attendez pas à ce que ce soit aussi simple que de dire "ah oui vous avez complètement raison je suis trop conne", donc je vous montre des manières de faire à la Casey, qui sont pas forcément syntaxiquement agréables (moi je préfère des classes plutôt que des struct + fonctions libres et des const& plutôt que des *), mais ce qu'il faut voir c'est le principe en lui même, après il y a différentes implémentations possibles qu'on peut rendre plus "safe" si c'est ce que vous voulez.

                        Par exemple l'ECS c'est un concept que je trouve super, par rapport à avoir un ennemi qui est un être vivant et des ennemis de feu et de glace qui sont des ennemis et des ennemis de feu à pattes qui sont des ennemis de feu, etc. Et vous aussi vous utilisez ce concept, donc c'est bien d'échanger des idées etc.

                        Et quand j'entends que vous dites que le C++ est de loin un langage très compliqué à apprendre, j'aimerais bien savoir ce que vous entendez par là. J'ai fais quelques réponses "sérieuses" à d'autres topics et pour moi tout ce que j'ai pu dire c'est juste de la pisse et je ne vois pas à quel moment c'est compliqué, que ce soit pour faire du SFNIAE ou autre. (je suis encore arrivée 4 heures en retard du live mais j'ai vu vite fait la rediff et d'ailleurs c'est qui la personne qui a parlé de Handmade Hero mdrrrr vous voyez je suis pas la seule)

                        • Partager sur Facebook
                        • Partager sur Twitter
                          28 mai 2022 à 15:23:53

                          Le problème c'est que tu n'as pas les compétences pour juger ce qui est le mieux entre la méthode Casey et le C++ moderne, on l'a vu dans tous les codes que tu as proposé (le fameux "oups" avec les shared_ptr où y'avait en fait aucun problème), on le voit dans tes explications (au pif, std::move sur un return est inutile, voir peut même bloquer certaines optimisations comme la NRVO), on le voit quand tu dis "ah std::vector va allouer de la mémoire alors qu'on pourrait faire les choses mieux" (au pif, c'est pas pour rien que le second paramètre template de std::vector est un allocateur mémoire).

                          Partant du constat que tu n'as pas (encore) les compétences pour juger du bien fondé de ce que disent certaines personnes comme Casey, ou même nous, à quoi bon discuter si tu n'es pas prête à te remettre en question (parce que tu n'es pas là pour ça, tu es là pour nous prouver que Casey fait mieux que tout le monde).

                          • Partager sur Facebook
                          • Partager sur Twitter

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

                            28 mai 2022 à 15:41:01

                            JadeSalina a écrit:

                            Oui on pourrait mettre une vérification dans l'allocateur, et renvoyer un nullptr si ça a échoué, mais de toute façon si y'a plus de mémoire on peut pas faire grand chose,

                            Ce n'est même pas que tu pourrais renvoyer un nullptr si cela échoue qui est le problème...

                            C'est que chaque ligne de code qui essaye de déréférencer le pointeur qui n'a pas été correctement alloué corrompra (au pire) ou fera planter (au mieux) inexorablement ton programme.

                            Il ne suffit pas de se dire que "on peut pas faire grand chose", car ce qui va se passer, c'est peut être des heures, voir, des jours d'exécution de ton programmes qui seront perdu(e)s.  Et encore, tu pourras t'estimer heureuse si le plantage de ton programme ne fait pas exploser la fusée que tu  voulais envoyer en plein vol!

                            JadeSalina a écrit:

                            Le but n'est pas d'insérer ou supprimer au milieu comme un tableau dynamique, si on veut faire ça on peut faire une liste chainée,

                            A ceci près qu'une liste chainée est -- effectivement -- génliale pour tout ce qui est ajout ou insertion d'élément (à condition d'avoir déjà déterminé l'endroit où l'insertion se fera) car elle se fait en temps constant (on parle d'une complexité en O(1) pour exprimer le fait que, quel que soit le temps nécessaire pour une action, il sera identique quel que soit l'endroit où l'action a lieu), mais que, tout ce que l'on gagne en facilité d'insertion, on le perd en possibilité de recherche et de tri, car la recherche se fait -- elle -- toujours en temps proportionnel (on parle d'une complexité en O(N) pour exprimer le fait que, quel que soit le temps utilisé pour la comparaison de la donnée, il sera multiplié par le nombre de données que tu devra comparer avant de trouver celle qui t'intéresse).

                            Or, sur un tableau d'éléments contigus,la recherche peut se faire -- surtout s'il est trié à la base -- dans un temps logarithmique (on parle d'une complexité en O(n *log(N)), ou même de dichotomie )

                            Je ne rejette pas ta solution, qui peut s'avérer intéressante dans certaines conditions... Je met juste un gros OUI, MAIS car les circonstances risquent fort de la disqualifier.

                            JadeSalina a écrit:

                            Il se passerait la même chose, dès qu'une erreur est détectée, on l'indique, on remplit optionnellement le stream d'erreurs, et c'est tout non ? Y compris si on a déjà fait des allocations (il suffit de les "oublier" en faisant un restore)

                            Encore faut il qu'elle soit détectée...

                            Tu ne détectera déjà pas si malloc échoue, alors, pour le reste...:'(:-°

                            Oui, je sais, je fais une fixette sur l'allocation de la mémoire en particulier et sur la validité des pointeurs de manière plus générale... Je la fais cependant pour une bonne raison: le fait de déréférencer d'un pointeur invalide est la deuxième cause de résultats aberrant et de crachs d'un programme, après l'étourderie de l'utilisateur.

                            En outre, si tu estime inutile de vérifier ton pointeur avant de le déréférencer, que "y a pas de risques à  le faire", cela laisse déjà présager qu'il y aura quantité d'autres situations dans lesquelles tu  risques d'estimer que "bah, y a pas besoin de vérifier cette donnée"... Tu es donc vraiment mal barre si tu espère comme cela détecter toutes les erreurs qui pourraient survenir ;)

                            Quand on te dit que ton code C est fondamentalement bugué s'il n'a pas un if toutes les deux lignes, dis toi bien que l'on sait de quoi on parle et que c'est la seule manière que tu aies de garantir que toutes les erreurs seront détectées ;)

                            JadeSalina a écrit:

                            Ici il suffit de faire un break pour ne pas lire les suivants

                            Bien sur, qu'il "suffit de". C'est d'ailleurs tellement pratique, les "il suffit de"...

                            Tellement pratique que, lors du développement d'Ariane V -- mais peut-être es tu trop jeune pour savoir de quoi je parle vu que c'était en 1996? -- un ingénieur a dit "il suffit de reprendre la centrale inertielle de Ariane IV, cela nous fera économiser plein de sous".

                            Il avait juste oublié de prendre en compte que la puissance des moteurs de Ariane V était incroyablement supérieure à celle des moteurs de Ariane IV.

                            A un point tel que, cette centrale inertielle étant placée dans Ariane V, l'une des valeurs traitée est devenue plus importante que la valeur maximale qui avait jamais été envisagée pour Ariane IV, et pour cause: sur Ariane IV, la plus haute valeur n'aurait pas excédé la moitié de cette valeur maximale.

                            Le résultat des courses, c'est que cette valeur a, à un moment donné, été considérée comme nulle, parce qu'il manquait un bit pour pouvoir la représenter correctement.  La valeur avait, tout simplement, fait "un tour complet" de toutes les possibilités de représentation du système.

                            Manque de bol, cette valeur était utilisée comme ... diviseur dans une division, ce qui a occasionné une tant redoutée "division par 0".  Evidemment, le programme qui utilisait le résultat renvoyé par la centrale inertielle s'en est rendu compte, et il a réagit de manière logique" bon, la centrale inertielle numéro 1 est foutue, mais, comme j'en ai une autre de réserve, c'est pas un problème, je n'ai qu'à utiliser celle de réserve".

                            Seulement, voilà... C'était exactement le même boitier. Une même cause occasionnant les mêmes effets, le programme a de nouveau eu le même problème de division par 0. il a donc réagit sous une forme proche de "ouch, voilà que la deuxième centrale  inertielle me lâche, et je n'en ai plus de réserve... Bon, ben, tant pis, je fais exploser le bastringue".

                            Ariane V a fini par exploser 28 secondes après son décollage, emportant avec elle des satellites pour une valeur totale de 370 millions de dollars. Tout cela parce qu'un ingénieur espérait économiser l"équivalent de 120 000 euros grâce à un "il suffit de".

                            Bien sur, tout le monde ne travaille pas dans l'aérospatiale. Cette histoire devrait cependant te convaincre de te méfier comme de la peste  des "il suffit de", car, même si les conséquences ne seront pas forcément aussi catastrophiques, un "il suffit de" qui part sur une assertion erronée aura toujours des conséquences désastreusesà ton niveau ;)

                            JadeSalina a écrit:

                            mais que se passe t'il en cas de succès ? Et bien il faudra lire tous les fichiers... Donc est-ce si grave que ça d'ouvrir les autres fichiers alors qu'on sait qu'il y a une erreur ? Ce que je veux dire, c'est que si on est capables de lire tous les fichiers en cas de succès, on peut aussi les lire en cas d'échec, ça va pas couter plus cher, puisque on est censé pouvoir le faire en cas de succès. C'est juste que ça "gâche" un peu de CPU en cas d'échec puisqu'on pourrait éviter d'ouvrir les autres mais il faudrait le faire en cas de succès (qui peut le plus peut le moins).

                            Si ce n'était que le fait de "gacher quelques cycles CPU", ce pourrait être simplement anecdotique...

                            Seulement, tu manque encore une fois cruellement de recul...

                            Encore une fois, le problème ne vient pas du "cout" de la lecture des autres fichiers en cas d'échec, surtout si tu parle du temps qu'il faudra pour les lire.

                            Le problème vient de toutes les données que tu n'auras pas pu extraire du fichier en échec, parce que cela signifie que tes données -- ou, à tout le moins toute une partie de tes données -- seront tout bonnement incohérentes.

                            Le problème, lorsque tu manipule ne serait-ce qu'une seule donnée incomplète ou incohérente, c'est que tu ne peux absolument pas te fier au résultat de la manipulation, qui pourrait au demeurant te semble "parfaitement valide".

                            Or, dans la plupart des cas, nous ne faisons subir un traitement à nos données que parce que le résultat de ce traitement nous sera utile pour effectuer d'autres traitements "plus complexes". Et si le résultat d'un seul traitement "aussi  simple soit il" n'est pas fiable, c'est la fiabilité de tous les traitements qui utiliseront ce résultat de manière directe ou indirecte qui sera remise en cause

                            Pour que tu comprenne bien, mettons que j'ai une donnée a (peut importe son type ou sa valeur) dont la valeur est "incohérente". Et j'entrepends le traitement A -- qui va me donner comme résultat la donnée b -- à partir de ma donnée a. Je ne peux déjà pas me fier à b, vu que je ne pouvais pas me fier à a.

                            Manque de bol, b est utilisé par le traitement C qui me renvoie la donnée c. Je ne peux donc pas me fier à la valeur de c, vu que je ne pouvais déjà pas me fier à la valeur de b.

                            Au final, c'est l'ensemble de tous les traitements que ton programme pourrait entreprendre qui pourrait être compromis, et tout cela, à cause d'une seule valeur corrompue ou incohérente.

                            Peu importe ce à quoi peut bien servir ton programme.  Peu importe le temps qu'il mettra à te fournir le résultat que tu attend.  Peu importe le nombre de données que tu attend au final.

                            Le fait est que, si tu laisses passer une seule donnée corrompue ou incohérente, tu vas au final te retrouver avec un résultat totalement incorrect, alors que tout te porte à  croire qu'il est... tout à  fait juste.

                            Si, en plus, tu sais "qu'il y a eu un problème" -- parce qu'il y a eu un joli message "j'ai pas su lire le fichier n°7, mais je continue quand même" -- mais que tu ne sais pas dans quelle mesure ce problème aura pu avoir un impact sur le résultat final, tu vas regarder ce résultat et te demander "Où est l'erreur?". Belle séance d'arrachage de cheveux en perspective :'(

                            -
                            Edité par koala01 28 mai 2022 à 16:47:15

                            • 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
                              28 mai 2022 à 17:16:17

                              Pffff, flemme. C'est le week end.

                              Jade, j'essayais juste de t'expliquer que ce que tu proposes est connu, que c'est une solution parmis d'autres,  qu'elle présente des défauts, et que si les devs C++ ne font pas comme ça tout le temps, c'est qu'il y a des bonnes raisons.

                              Libre a toi d'essayer de comprendre ou de faire l'autruche.

                              -
                              Edité par gbdivers 28 mai 2022 à 17:19:40

                              • Partager sur Facebook
                              • Partager sur Twitter
                                28 mai 2022 à 17:34:07

                                koala01 a écrit:

                                Il ne suffit pas de se dire que "on peut pas faire grand chose", car ce qui va se passer, c'est peut être des heures, voir, des jours d'exécution de ton programmes qui seront perdu(e)s.  Et encore, tu pourras t'estimer heureuse si le plantage de ton programme ne fait pas exploser la fusée que tu  voulais envoyer en plein vol!

                                On n'a pas le droit aux allocations dynamiques dans les fusées :p . (Ni aux allocateurs dégueux comme ceux de Casey, heureusement).

                                • Partager sur Facebook
                                • Partager sur Twitter

                                Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                                  28 mai 2022 à 17:44:03

                                  Pour la dichotomie sur un tableau trié, la complexité est en O(log2 n). On dira en O(log n) car on est à une constante près.
                                  • Partager sur Facebook
                                  • Partager sur Twitter

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

                                    28 mai 2022 à 18:24:37

                                    Ksass`Peuk a écrit:

                                    On n'a pas le droit aux allocations dynamiques dans les fusées :p . (Ni aux allocateurs dégueux comme ceux de Casey, heureusement).

                                    En effet, c'est juste pour exprimer avec force ce que chantait si bien benabar:

                                    C'est l'effet papillon, petites causes, grandes conséquences...

                                    • 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
                                      28 mai 2022 à 19:10:36

                                      (le fameux "oups" avec les shared_ptr où y'avait en fait aucun problème)

                                      Je remet le code pour référence :

                                      class RessourceManager {
                                      public:
                                        void add_ressource(std::shared_ptr<Ressource> ressource) {
                                          ressources.push_back(std::move(ressource));
                                        }
                                       
                                      private:
                                        std::vector<std::shared_ptr<Ressource>> ressources;
                                      };
                                       
                                      std::shared_ptr<Ressource> load_ressource(std::filesystem::path path) {
                                        return std::make_shared<Ressource>(path, ...);
                                      }
                                       
                                       
                                      // Quelque part dans le code
                                      auto my_ressource = load_ressource("fichier.txt");
                                      my_ressource->set_quelque_chose(...);
                                       
                                      get_manager().add_ressource(my_ressource); // OOOUUUPPS

                                      Oui tel quel il n'y a pas de problème apparent, le shared_ptr de my_ressource sera détruit en sortant du scope, et quand le manager sera détruit, il détruira le shared_ptr stocké dans le std::vector, libérant ainsi la ressource. Houra ! (on passera sur la copie inutile). Par contre là où c'est plus méchant, c'est si il y a du  code juste après, comme par exemple la boucle principale, ce qui fait qu'on déclare les ressources, et on part dans la main loop, ce n'est pas si déconnant que ça comme idée quand même. Et bien c'est là qu'il y a un problème puisque le std::move oublié fera que notre my_ressource restera actif et empêchera la ressource de se libérer. Et le compilateur ne nous préviendra pas.

                                      au pif, std::move sur un return est inutile, voir peut même bloquer certaines optimisations comme la NRVO

                                      Evidemment que oui, je parlait de std::move les infos, tel quel ça va les copier pour rien, et encore une fois on aura pas d'erreur à la compilation

                                      au pif, c'est pas pour rien que le second paramètre template de std::vector est un allocateur mémoire

                                       Merci j'attendais cette réponse, donc vous reconnaissez que ça peut être intéressant d'avoir des allocateurs manipulés explicitement pour éviter de faire un festival d'allocations dynamiques, si on sait par exemple qu'on va toujours allouer en ordre LIFO (qui est le type d'allocation le plus rapide qui soit, c'est pas pour rien que les variables locales sont allouées comme ça, sur la fameuse "pile"). Après je dis pas qu'il faut absolument faire comme ça systématiquement et dans tout le programme, mais vous reconnaissez quand même que ça peut être intéressant dans quelques systèmes isolés bien choisis, qui sont plus "critiques" (au sens de la performance).

                                      Partant du constat que tu n'as pas (encore) les compétences

                                      C'est ce que vous me dites à chaque fois, mais honnetement je ne crois pas, je pense que j'ai tout à fait les compétences. Envoyez moi des questions/exos sur le C++ moderne et vous verrez bien que je le connais excessivement bien. D'ailleurs pourquoi vous dites que c'est hyper dur à apprendre, parce que pour moi c'est très facile, donc soit il me manque 90% des connaissances, soit je le maitrise effectivement, mais je penche pour la 2ème option.

                                      Encore faut il qu'elle soit détectée...

                                      Tu ne détectera déjà pas si malloc échoue, alors, pour le reste...:'(:-°

                                       Là dans l'exemple je vérifie pas les erreurs d'allocation, mais je pourrais très bien le faire, comment ça je ne détecterais pas l'erreur ?

                                      Le problème, lorsque tu manipule ne serait-ce qu'une seule donnée incomplète ou incohérente, c'est que tu ne peux absolument pas te fier au résultat de la manipulation, qui pourrait au demeurant te semble "parfaitement valide".

                                      Mais bien sûr que oui que si une opération a échouée, il faut pas s'attendre à avoir des données fiables, c'est pour ça qu'il y a un "has_errors" dans mon exemple avec le Layout et le File. Quand une fonction manipule le truc, elle prévient met "has_errors" à true, et à partir de là, il ne faut pas lire ce qu'il y a dedans. Et si vous trouvez que c'est "error prone" vous pouvez planquer le check derrière un operator bool et operator->, à la manière d'un std::optional.

                                      Donc les "if toutes les deux lignes" peuvent se résument en un "if (handle.has_errors)", ce qui fait que si le truc est pas valide on n'y touche pas (comme ici par exemple https://github.com/jojolatina/etl/blob/ad5fa8c0297ea6dd3ccffbca4d2c890ddc8b1b4e/export.cpp#L6)

                                      JadeSalina a écrit:

                                      Ici il suffit de faire un break pour ne pas lire les suivants

                                      Bien sur, qu'il "suffit de". C'est d'ailleurs tellement pratique, les "il suffit de"...

                                      Pour autant que je sache, le break permet effectivement de ne pas ouvrir les fichiers suivants dès qu'une erreur est détectée

                                      Ksass`Peuk a écrit:

                                      koala01 a écrit:

                                      Il ne suffit pas de se dire que "on peut pas faire grand chose", car ce qui va se passer, c'est peut être des heures, voir, des jours d'exécution de ton programmes qui seront perdu(e)s.  Et encore, tu pourras t'estimer heureuse si le plantage de ton programme ne fait pas exploser la fusée que tu  voulais envoyer en plein vol!

                                      On n'a pas le droit aux allocations dynamiques dans les fusées :p . (Ni aux allocateurs dégueux comme ceux de Casey, heureusement).


                                      Comment l'allocateur de Casey peut-il être dégueu alors que c'est le meilleur possible au sens de la performance, simplicité et fiabilité ? Je vous accorde qu'il est peut être trop basique pour certains besoins comme allouer/désallouer des tailles quelconques dans n'importe quel ordre (ça ça s'appelle un "general purpose allocator", mais ça a un certain coût au runtime).

                                      Comme vous le voyez dans l'exemple de ma fonction "faire_quelquechose" ci-dessus, l'allocateur de Casey permet d'allouer la mémoire dont on a besoin, et la libérer très rapidement, ce qui parait justement intéressant dans le contexte de l'embarqué (mais il faudrait peut être demander à Maitre Helba), ou quand c'est exactement ce qu'on veut faire (les fameux 90% de Casey). Et il pourrait fonctionner quelque soit la provenance de la mémoire (là on fait un malloc mais c'est trivial à remplacer), si vous n'avez pas d'allocation dynamique possible, vous mettez la mémoire que vous voulez, vous avez le droit à de la mémoire quand même non ? D'ailleurs les interfaces des fusées de SpaceX sont codées en Javascript, mais peut être que vous voulez parler du "coeur" de la fusée.

                                      Et d'ailleurs ce n'est pas "l'allocateur de Casey", ça s'appelle une arena, ou bump allocator, ou stack allocator, ... (ce n'est pas un memory pool car les allocations peuvent être de taille quelconque)

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        28 mai 2022 à 20:05:19

                                        l'allocateur de Casey permet d'allouer la mémoire dont on a besoin, et la libérer très rapidement, ce qui parait justement intéressant dans le contexte de l'embarqué (mais il faudrait peut être demander à Maitre Helba)

                                        ça dépend de la machine ,mais y'a pas d'allocation mémoire en embarqué ,tu aura juste plusieurs buffer de tes données tout simplement (ce qui fait que tu peux utiliser plus de mémoire que tu en a réellement besoin).

                                        La seconde méthode (quelque fois combiné avec le premier) c'est tout simplement de faire une allocation mémoire du type LIFO. (et tu vas me dire c'est ce que fais et casey aussi) , oui pour ça que qu'il réinvente pas la poudre.

                                        Mais ça a ces contraintes et ces points forts.
                                        Que tu connais apparemment : " si on sait par exemple qu'on va toujours allouer en ordre LIFO"

                                        Parce que un allocateur LIFO , forcément tu pourra pas libérer ce que tu veux , mais seulement celui du premier élément alloué , bref tu devra alloué et désalloué dans l'ordre.

                                        C'était utilité aussi dans le JV pour avoir un contrôle plus fin sur des ressources limité. (surtout que y'avait forcément des ressources qu'on gardait pendant tout le jeu en mémoire, donc qu'on alloué au début les données persistante).
                                        Et c'est là que ça me fait "rire"  : "Comment l'allocateur de Casey peut-il être dégueu alors que c'est le meilleur possible au sens de la performance, simplicité et fiabilité ?"
                                        T’inquiète pas tes vieux Zelda sur N64 ou GameCube, ils ont pas attendu Casey pour faire ça :)
                                        Par contre je doute que l'implémentation que fait Casey soit bonne...

                                        Et tu sais que " meilleur possible au sens de la performance, simplicité et fiabilité" , n'est pas un argument valide ?
                                        Par exemple une Twingo , est meilleur qu'un 4x4 pour rouler en ville , et inversement, je te déconseille d'utiliser ta Twingo pour aller en montagne sans route.
                                        C'est pour ça qu'on appelle un "ingénieur" , une personne qui va utiliser certaine technique les plus adapté aux besoins.
                                        Donc , il n y'a pas de plus simple/fiable/performant , cela dépend toujours d'un contexte.

                                        Comme dit GB :

                                        Mais je le répète , cette méthode est pas parfaite 'est une solution parmi d'autres, qu'elle présente des défauts, et que si les devs C++ ne font pas comme ça tout le temps, c'est qu'il y a des bonnes raisons.

                                        Et c'est un peu ton soucis de nous prendre pour des débutants ,bien sur que tout le monde ici connaît cette technique.
                                        On a pas besoin de Casey pour connaître cette technique certain ici l'utilise (dont moi).

                                        Le pire c'est que tu le dit toi même :

                                        Après je dis pas qu'il faut absolument faire comme ça systématiquement et dans tout le programme, mais vous reconnaissez quand même que ça peut être intéressant dans quelques systèmes isolés bien choisis, qui sont plus "critiques" (au sens de la performance).

                                        Comme tu dis , ça marche que pour quelque système isolé , et l'embarquée , c'est un domaine à part.
                                        Ce que je veux dire, c'est que beaucoup de logiciel sont un peu plus compliqué que ce que tu fais en embarquée ou même dans des "vieux Jeux Video comme Zelda sur Wii" avec un allocateur du type LIFO.
                                        Que t'as besoin uniquement de ce type d'allocation tant mieux , mais il est souvent insuffisant pour pas mal de cas.
                                        Pour la question "d'optimisation" , ce n'est pas ce qui est le plus gourmand sur un programme.
                                        Il a juste l'avantage d'avoir un contrôle fine sur un Buffer limité (si tu as une contrainte de RAM assez forte) ( moi je mettait un offset pour savoir combien j'allouais , et on peut fixer une limite de l'allocation si l'offset dépasse le buffer et connaître la mémoire alloué via l'offset).

                                        Je vais te dire l'erreur que tu fais depuis le début , tu ignore la complexité de l'ensemble de l'informatique , les informaticiens sont pas des débiles qui font des choses complexes parce que c'est cool.
                                        Si certain informaticien utilise pas cette technique ,c'est qu'elle ne convient pas à leur besoin.
                                        Donc inutile d'emmerder tout le monde avec ce que dit Casey et l'avantage/inconvénient de l'allocation LIFO.

                                        -
                                        Edité par HelbaSama 28 mai 2022 à 21:55:46

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          28 mai 2022 à 21:22:43

                                          JadeSalina a écrit:

                                          Merci j'attendais cette réponse, donc vous reconnaissez que ça peut être intéressant d'avoir des allocateurs manipulés explicitement pour éviter de faire un festival d'allocations dynamiques,

                                          Pas du tout... TU es arrivée avec le "soi disant problème" lié à la bibliothèque standard de ne pas pouvoir choisir la politique d'allocation de mémoire qui te convient.

                                          Nous t'avons juste signalé que cela fait plus de trente ans que ce problème (qui n'en est en réalité pas un) a été pris en compte... Faudrait quand même voir à pas trop inverser les rôles ;)

                                          En outre, il faut quand même te rendre compte que, hormis dans des circonstances très particulières, dues, par exemple, à un environnement particulièrement restreint en termes de ressources, l'idée de devoir mettre en place une politique particulière d'allocation de mémoire ne fera sans doute jamais partie de tes priorités parce que "tu as d'autres chats à fouetter".

                                          Or, il se fait que la politique utilisée par défaut est suffisamment efficace et suffisamment proche de ce que tu ferais (sans doute mal) toi-même dans ton coin pour que tu n'aie même jamais besoin de t'en inquiéter.

                                          On va faire simple: jusqu'à présent, je n'ai jamais croisé un projet -- aussi complexe soit-il -- pour lequel les développeurs auraient jugé utile (ou indispensable) de définir une politique alternative d'allocation de la mémoire.

                                          JadeSalina a écrit:

                                          Partant du constat que tu n'as pas (encore) les compétences

                                          C'est ce que vous me dites à chaque fois, mais honnetement je ne crois pas, je pense que j'ai tout à fait les compétences. Envoyez moi des questions/exos sur le C++ moderne et vous verrez bien que je le connais excessivement bien. D'ailleurs pourquoi vous dites que c'est hyper dur à apprendre, parce que pour moi c'est très facile, donc soit il me manque 90% des connaissances, soit je le maitrise effectivement, mais je penche pour la 2ème option.

                                          Hé bien, au risque de prendre le contrepieds de certains intervenants, je crois que ton véritable problème n'est pas un manque de compétences, car je pense même que tu es quelqu'un de compétent.

                                          Par contre, ce qui fait pour l'instant de toi un mauvais développeur, c'est le manque d'expérience et de recul. C'est le fait que, par manque d'expérience et de recul tu en vienne à considérer ce que dit un type qui mériterait juste que l'on bloque son compte de créateur de contenu sur twich (ou youtube ou autre) comme parole d'évangile.

                                          S'il y a une seule chose qui te rende incompétente à nos yeux (ou, à tout le moins, aux miens), c'est clairement cela ;)

                                          JadeSalina a écrit:

                                          Pour autant que je sache, le break permet effectivement de ne pas ouvrir les fichiers suivants dès qu'une erreur est détectée

                                          Encore une fois, tu n'as pas compris... A force, je vais finir par croire soit que tu fais exprès de ne pas comprendre, soit que tu fais semblant de ne pas comprendre pour voir jusqu'où la discussion pourra aller ...

                                          Le problème n'est pas savoir comment éviter de lire le fichier suivant si une erreur a été détectée!!!!

                                          Bien sur que le break pourrait être utilisé et qu'il sert -- a priori -- justement à cela. Personnellement je pourrais  très facilement te proposer trois autres solutions en plus du break pour arriver au même résultat, et pourtant, j'en choisirais sans doute une quatrième, qui serait ma préférée, et qui n'utiliserait ni break, ni goto, ni return ni exceptions dans la boucle (pour autant que je puisse avoir la garantie formelle que toutes les erreurs seront correctement détectées ;) ).

                                          JadeSalina a écrit:

                                           Là dans l'exemple je vérifie pas les erreurs d'allocation, mais je pourrais très bien le faire, comment ça je ne détecterais pas l'erreur ?

                                          Tu ne teste déjà pas le retour de ton malloc ... Je vois donc deux solutions:

                                          • Soit tu te dis que "ce n'est jamais qu'un exemple, je vais pas me faire chier à faire les vérifications indispensables"
                                          • Soit tu de dis -- comme tu l'as si bien dit plus haut -- que "de toutes facons, si l'allocation échoue on ne sait quand même rien faire"

                                          Par pitié, dis moi que tu es dans le premier cas!!!!

                                          Car, autrement, dans combien de situations "susceptibles d'échouer" vas tu partir du principe que "de toutes facons, si ca échoue, on ne peut quand même rien faire"?

                                          Comment veux tu avoir la moindre chance de détecter une erreur avec un telle mentalité?

                                          Le pire, c'est qu'à lire tes différentes interventions, tu  semble plutôt être dans le deuxième cas :'(

                                          JadeSalina a écrit:

                                          Comment l'allocateur de Casey peut-il être dégueu alors que c'est le meilleur possible au sens de la performance, simplicité et fiabilité ?

                                          1. alors que c'est le meilleur possible au sens de la performance: Attention, dés que l'on parle de performances, il s'agit de pouvoir prouver ses dires...Où sont les benchmarks pour le prouver? Ah,y en a aucun? voila qui est bien moche :p
                                          2. Simple : ca, je peux te l'accorder... c'est le genre de truc que j'écrivais déjà en C avant même dévoir suivi mes cours d'informatique ;)
                                          3. et fiabilité : S'il est comme le code que toi tu nous montre, sans aucune vérification du retour de chaque malloc, tu repassera pour la fiabilité :p

                                          Donc, oui, je crois que l'on peut effectivement parler d'un "allocator dégueu"...

                                          JadeSalina a écrit:

                                          Mais bien sûr que oui que si une opération a échouée, il faut pas s'attendre à avoir des données fiables, c'est pour ça qu'il y a un "has_errors" dans mon exemple avec le Layout et le File. Quand une fonction manipule le truc, elle prévient met "has_errors" à true,

                                          Et, juste pour rire, tu compte faire comment pour faire passer ton has_errors à true, alors que tu ne semble pas vouloir prendre la peine de vérifier les choses essentielles? Par l'action du saint esprit?

                                          -
                                          Edité par koala01 28 mai 2022 à 22:01:14

                                          • 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
                                            29 mai 2022 à 11:40:59

                                            JadeSalina a écrit:

                                            Comment l'allocateur de Casey peut-il être dégueu alors que c'est le meilleur possible au sens de la performance, simplicité et fiabilité ? Je vous accorde qu'il est peut être trop basique pour certains besoins comme allouer/désallouer des tailles quelconques dans n'importe quel ordre (ça ça s'appelle un "general purpose allocator", mais ça a un certain coût au runtime).

                                            J'imagine bien l'audit de sûreté.

                                            - Qu'est ce qui garantit que vous n'avez aucun risque de remplir la mémoire ?
                                            - Eh bien voyez vous, on utilise un allocateur manuel.
                                            - ...
                                            - Il est rapide et en plus pour ne pas avoir de soucis, il suffit de ne pas oublier les libérations !
                                            - ...
                                            - Et, juré, on en a oublié aucune !
                                            - Ça a l'air bien, si vous l'enlevez, peut être qu'on autorisera votre fusée à voler.

                                            -
                                            Edité par Ksass`Peuk 29 mai 2022 à 11:41:12

                                            • Partager sur Facebook
                                            • Partager sur Twitter

                                            Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                                              29 mai 2022 à 13:12:28

                                              Pas du tout... TU es arrivée avec le "soi disant problème" lié à la bibliothèque standard de ne pas pouvoir choisir la politique d'allocation de mémoire qui te convient.

                                              Oui mais j'avais pas dit explicitement qu'on ne pouvait pas changer l'allocateur, j'ai laissé sous entendre qu'en déclarant des std::vector et autre n'importe où ça ne permettait pas de faire comme Casey à savoir d'allouer d'une façon déterministe à partir d'un bloc préalloué.

                                              En outre, il faut quand même te rendre compte que, hormis dans des circonstances très particulières, dues, par exemple, à un environnement particulièrement restreint en termes de ressources, l'idée de devoir mettre en place une politique particulière d'allocation de mémoire ne fera sans doute jamais partie de tes priorités parce que "tu as d'autres chats à fouetter". Or, il se fait que la politique utilisée par défaut est suffisamment efficace et suffisamment proche de ce que tu ferais (sans doute mal) toi-même dans ton coin pour que tu n'aie même jamais besoin de t'en inquiéter.

                                              Je suis COMPLETEMENT d'accord, si le truc par défaut est raisonnable et qu'on a d'autres chats à fouetter, c'est idiot d'essayer de faire un truc sur mesure au lieu de se concentrer sur les vrais problèmes du client. Le problème c'est quand on a pas d'autres chats à fouetter, et qu'on veut faire un truc plus optimal, le faire tourner sur le web donc faut qu'il soit léger, etc. Tout d'un coup, les "trucs par défaut" ne sont plus si raisonnables que ça...

                                              Tu ne teste déjà pas le retour de ton malloc ... Je vois donc deux solutions: Soit tu te dis que "ce n'est jamais qu'un exemple, je vais pas me faire chier à faire les vérifications indispensables" Soit tu de dis -- comme tu l'as si bien dit plus haut -- que "de toutes facons, si l'allocation échoue on ne sait quand même rien faire"

                                              L'implémentation que j'ai faite de l'allocateur n'est pas sérieuse du tout, je sais même pas si ça compile, c'était juste pour donner l'idée "en gros", donc j'ai pas fait beaucoup de vérifications.

                                              Par contre dans le cas particulier de la mémoire, déjà on peut pas faire grand chose sans, vous en conviendrez, et ensuite je dirais même qu'il vaut mieux allouer un gros bloc mémoire dès le début, comme ça on sait que si ça échoue à ce moment là, on n'aura pas assez de mémoire pour faire tourner le truc. Alors que si on a des std::vector ou std::string qui allouent n'importent quand "chacun de leur côté", justement c'est là que ça peut fail plus facilement. Et je rappelle que si on alloue 50 To (téra) de mémoire, ça veut pas dire que cette mémoire sera utilisée par le programme, on peut juste la réserver (pour faire ça il ne faut pas passer par new ni même malloc, il faut appeler l'OS).

                                              alors que c'est le meilleur possible au sens de la performance: Attention, dés que l'on parle de performances, il s'agit de pouvoir prouver ses dires...Où sont les benchmarks pour le prouver? Ah,y en a aucun? voila qui est bien moche :p

                                              Oui tout à fait, les processeurs modernes sont trop complexes pour qu'on puisse supposer quoi que ce soit quant à la performance d'un programme sans l'avoir réellement fait tourner. Mais ne faisons pas l'autruche pour autant, un allocateur qui fait juste des additions/soustractions a toutes les chances d'être plus rapide qu'un allocateur qui cherche des blocs libres, défragmente si besoin en réorganisant les blocs, etc.

                                              Et, juste pour rire, tu compte faire comment pour faire passer ton has_errors à true, alors que tu ne semble pas vouloir prendre la peine de vérifier les choses essentielles? Par l'action du saint esprit?

                                              Je ne comprends pas bien ce à quoi vous faites référence ? Les personnes qui mettent has_errors à true c'est les fonctions qui manipulent le truc, par exemple "open_file", etc. L'utilisateur a juste besoin de se demander si il y a une erreur si il veut la gérer d'une manière ou d'une autre. De même, les opérations qui ne peuvent pas fonctionner en cas d'erreur peuvent vérifier que le truc est valide avant de lancer un traitement, un peu comme pourrait le faire "close_file", sans avoir besoin de "polluer" le code utilisateur.

                                              Et vu que l'erreur est stockée dans le "handle", elle peut remonter sans problème, comme dans mon exemple, si en parsant le fichier on rencontre une erreur, on met has_errors à true et on s'arrête. Ou alors si on arrive pas à ouvrir le fichier, ou alors si on a pas assez de mémoire, etc. Dans tous les cas, le code utilisateur a juste a lancer les opérations et vérifier à la fin si il y a "has_errors" à true, ce qui rend le code assez clair je trouve, sans avoir des if toutes les deux lignes. Un exemple vite fait :

                                              void main()
                                              {
                                                  Truc truc;
                                                  faire_quelque_chose(&truc);
                                                  faire_autre_chose(&truc);
                                                  /* ... */
                                              
                                                  if (truc.has_errors)
                                                  {
                                                      /* Ah, quelque chose a mal tourné, on peut informer l'utilisateur d'une manière ou d'une autre */
                                                      print(truc.errors);
                                                  }
                                              }

                                              Et les fonctions "faire_quelque_chose", etc, peuvent faire la vérification que le truc est valide, et ne rien faire si le truc a des erreurs, par exemple.

                                              J'imagine bien l'audit de sûreté. - Qu'est ce qui garantit que vous n'avez aucun risque de remplir la mémoire ? - Eh bien voyez vous, on utilise un allocateur manuel.

                                              Alors là j'avoue que j'ai du mal à comprendre, vous préférez mettre des std::vector et autre partout et donc faire tourner un allocateur "general purpose" qui peut prendre un temps indéterminé, et qui peut également fail à tout instant, plutôt qu'un allocateur dont l'allocation est "instantanée" et qui pourrait en plus ne jamais fail si on sait que toutes les allocations vont rentrer dans le premier bloc alloué au départ ?

                                              -
                                              Edité par JadeSalina 28 juillet 2022 à 0:48:51

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                29 mai 2022 à 13:49:34

                                                JadeSalina a écrit:

                                                Alors là j'avoue que j'ai du mal à comprendre, vous préférez mettre des std::vector et autre partout et donc faire tourner un allocateur "general purpose" qui peut prendre un temps indéterminé, et qui peut également fail à tout instant, plutôt qu'un allocateur dont l'allocation est "instantanée" et qui pourrait en plus ne jamais fail si on sait que toutes les allocations vont rentrer dans le premier bloc alloué au départ ? Si vous avez peur d'oublier de désallouer vous pouvez planquer le truc avec du RAII :

                                                C'est là qu'on voit que tu manques cruellement d'expérience.

                                                On a pas le temps ni le besoin, d'optimiser chaque partie de chaque programme le plus possible, surtout parce que le gain est minimal.

                                                Optimiser (surtout de façon assez poussée comme tu le suggères), ça rajoute de la complexité au code (par rapport à un allocateur générique mais éprouvé et bien plus testé), ça demande du temps, ça peut introduire des erreurs, tout ça pour quoi ? On s'en fiche de gagner 5ms sur une opération qui prend deux secondes.

                                                Un programme passe 90% de son temps dans 10% de son code, et les efforts d'optimisations doivent se concentrer sur ces parties-là, avec ce qu'on appelle du profiling (mesure de quelles parties du code prennent le plus de temps).

                                                L'allocateur générique est imparfait, mais il fonctionne très bien dans l'immense majorité des cas. Bien sûr si on avait un temps et des ressources infinies on pourrait optimiser un programme entièrement et gagner ces pouillèmes de millisecondes à des endroits où ça ne ferait aucune différence, mais en pratique on a pas de temps et de ressources infinies.

                                                (Au passage, tu as une source qui parle de la fameuse "défragmentation mémoire" de l'allocateur dont tu parles pour la seconde fois ? Parce que je ne vois pas comment l'allocateur générique pourrait défragmenter - et donc déplacer de la mémoire et péter tous les pointeurs - le seul que je vois qui puisse faire ça est celui qui fait le lien entre mémoire virtuelle et mémoire physique - l'OS - et il n'a pas besoin d'attendre une allocation mémoire pour faire ça, et toute la mémoire est concernée).

                                                • Partager sur Facebook
                                                • Partager sur Twitter

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

                                                  29 mai 2022 à 14:07:01

                                                  JadeSalina a écrit:

                                                  Alors là j'avoue que j'ai du mal à comprendre, vous préférez mettre des std::vector et autre partout et donc faire tourner un allocateur "general purpose" qui peut prendre un temps indéterminé, et qui peut également fail à tout instant, plutôt qu'un allocateur dont l'allocation est "instantanée" et qui pourrait en plus ne jamais fail si on sait que toutes les allocations vont rentrer dans le premier bloc alloué au départ ?

                                                  Heureusement que j'ai écris plus tôt que l'allocation dynamique n'est pas autorisée. En fait on fait beaucoup plus simple : la mémoire est réservée précisément pour chaque tâche et après, on ne fait plus aucun processus de demande. Il n'y a pas d'allocateur.

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter

                                                  Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                                                    29 mai 2022 à 14:30:21

                                                    Non mais ça en devient ridicule là...
                                                    Vraiment ce que j'ai le plus apprécié sur le C++ , c'est std::vector , et c'est vraiment un gros plus , critiquer ça me semble difficile....

                                                    Surtout quand tu propose de remplacer std::vector , par un allocateur LIFO , c'est une blague ?
                                                    Clairement tu manque d'expérience (et Casey aussi).
                                                    Et inutile de parler de ces projets de jeux video , c'est vraiment a part, non seulement ces jeux sont pas ultra poussé comme conception, mais faire un jeux avec allocateur LIFO , c'est ultra chiant.
                                                    Si tu veux faire un moteur de jeux avec , faut limite oublier.
                                                    Et ce que je te dis ,c'est pas de la théorie , c'est de la pratique.

                                                    Et c'est un peu ça le soucis, c'est quoi la contrainte qui t'oblige à faire ça ?
                                                    Tu tourne sur une machine de quelque Mo ?
                                                    Je ne pense pas vois-tu.

                                                    Perso quand je code sur PC , j'utilise oui std::vector "partout" , c'est pratique , tu peux allouer la taille d'un buffer à l'init, ça fait à boire et à manger.

                                                    Donc tu manque cruellement d’expérience , parce que ça en devient vraiment risible de proposer ce genre d'allocation contre un std::vector !

                                                    -
                                                    Edité par HelbaSama 29 mai 2022 à 14:37:17

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      29 mai 2022 à 14:42:35

                                                      mimalloc ça marche bien, aussi (on est passé à ça au taf, plutôt que de faire des allocateurs custom et refactorer les millions de lignes de code de nos produits)
                                                      • Partager sur Facebook
                                                      • Partager sur Twitter

                                                      Si vous ne trouvez plus rien, cherchez autre chose.

                                                        29 mai 2022 à 15:20:20

                                                        Oui bien sûr qu'on n'a pas "besoin" de passer tout son temps pour gagner 3 ms sur un truc d'1 seconde, peut être que le truc marche raisonnablement bien et qu'on s'en contente. Mais c'est une vision "pessimiste", pourquoi partir dans un truc qui pourra poser un problème plus tard et qu'il sera probablement trop lourd à refactorer, plutôt que de faire un truc simple comme Casey.

                                                        Vous allez me dire que c'est pas simple, mais en fait quand on voit comment le programme est structuré, tout s'articule correctement, sans pour autant que ce soit difficile à maintenir ou autre. J'ai jamais dit qu'on avait qu'un seul allocateur, on peut par exemple en avoir un pour de la mémoire permanente, qui ne sera libéré qu'à la fin et un autre pour de la mémoire temporaire utilisée à chaque frame, et encore mieux, il n'est même pas nécessaire de libérer la mémoire manuellement à chaque opération (et inutile aussi de mettre un wrapper RAII), puisqu'on peut supposer que ça sera de la mémoire "valide juste pour la frame courante" et qu'elle sera remise à zéro à la fin de la frame, sans avoir à y penser à la fin de chaque fonction qui alloue des trucs.

                                                        Pour la défragmentation je veux dire qu'à chaque free l'allocateur est libre de prendre le temps qu'il veut pour se rendre compte qu'il y a désormais plusieurs blocs libres consécutifs qu'il serait de bon ton de regrouper en un seul

                                                        mais faire un jeux avec allocateur LIFO , c'est ultra chiant.

                                                        Dans Handmade Hero que vous n'avez surement pas regardé, il ne passe jamais le moindre temps à gérer quoi que ce soit au niveau de la mémoire (il a aussi dit à plusieurs reprises que "memory management is a non issue" je peux vous envoyer les extraits). Dès qu'il veut de la mémoire pour faire des trucs, il l'alloue depuis l'allocateur temporaire (la "TransientArena") et il sera garanti que toute la mémoire allouée sera libérée à la fin de la frame (et pas libérée avec un "free", mais juste en remettant un compteur à zéro, on rince et on recommence).

                                                        Le fait que vous disiez que c'est chiant c'est que vous ne savez pas de quoi il s'agit puisque c'est très simple à utiliser. Et les std::vector on une politique d'agrandissement pas forcément optimale en terme d'utilisation mémoire (il y a des exemples ici https://ourmachinery.com/post/data-structures-part-1-bulk-data/, dans "allocation strategy").

                                                        Et dans Handmade Hero c'est même pas qu'il n'utilise pas de std::vector, c'est carrément qu'il n'y a aucun tableau dynamique, c'est à dire aucune structure permettant de push_back à gogo. Alors qu'on pourrait se dire à première vue que c'est impossible de s'en passer, mais quand on sait organiser son code comme le fait Casey, on se rend compte que dans la plupart des cas on peut s'en passer.

                                                        mimalloc ça marche bien, aussi (on est passé à ça au taf, plutôt que de faire des allocateurs custom et refactorer les millions de lignes de code de nos produits)

                                                        Vous confirmez mon premier paragraphe, évidemment que si vous avez déjà fait des millions de lignes ça va pas être possible de revoir toute la structure, mais quand le concept d'avoir des allocateurs et de savoir d'où vient la mémoire etc est pensé dès le départ, on peut facilement changer les choses. D'ailleurs pourquoi vous avez changé d'allocateur ? Ne me dites pas que vous aviez des problèmes de perf... Ni des problèmes de memory profiling pour savoir qui alloue quoi quand, puisque c'est justement trivial à faire avec la façon de Casey... (qui n'est d'ailleurs pas SA façon, plein d'autres gens font ce genre de choses, y compris sur de gros moteurs (cf. OurMachinery que j'ai linké plus haut) donc ça doit être un minimum viable comme méthode quand même)

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          29 mai 2022 à 15:36:09

                                                          T'en parleras à ceux qui ont créé les produits en Fortran dans les années 90, puis en C, puis en C++.

                                                          Et arrête de sortir handmade hero comme une référence, c'est pas son micro projet qui peut tenir lieu de référence.

                                                          EDIT

                                                          Eh oui, on ne maîtrise que rarement l'historique des projets sur lesquels on bosse...

                                                          -
                                                          Edité par dragonjoker 29 mai 2022 à 15:37:16

                                                          • Partager sur Facebook
                                                          • Partager sur Twitter

                                                          Si vous ne trouvez plus rien, cherchez autre chose.

                                                            29 mai 2022 à 15:42:43

                                                            JadeSalina a écrit:

                                                            Dans Handmade Hero que vous n'avez surement pas regardé, il ne passe jamais le moindre temps à gérer quoi que ce soit au niveau de la mémoire (il a aussi dit à plusieurs reprises que "memory management is a non issue" je peux vous envoyer les extraits). Dès qu'il veut de la mémoire pour faire des trucs, il l'alloue depuis l'allocateur temporaire (la "TransientArena") et il sera garanti que toute la mémoire allouée sera libérée à la fin de la frame (et pas libérée avec un "free", mais juste en remettant un compteur à zéro, on rince et on recommence).

                                                            Le fait que vous disiez que c'est chiant c'est que vous ne savez pas de quoi il s'agit puisque c'est très simple à utiliser.

                                                            ça reste chiant, pas besoin de regarde HH , et c'est là que tu es dans le fanboyisme , oui dans certain cas, ce genre d'allocateur pose pas de soucis.
                                                            Je connais le principe que ce genre d’allocation , il permet de réinitialisé à zero , merci Einstein , tu m'apprend rien.
                                                            Mais lui fait un petit jeu (vraiment).
                                                            Tente de faire un jeu plus complexe avec un allocateur LIFO est en reparlera ;)
                                                            (Pareil pour Casey le rigolo )
                                                            Et je dis pas que c'est impossible, tu peux faire un Zelda ou un Resident d'époque avec ça,  pas de soucis , mais c'est ultra pénible, parce que c'est un jeu un poil plus complexe que HH que tu met au sommet de l'oeuvre vidéoludique, son truc casse pas des briques vraiment.
                                                            Tu prend un Zelda, tu crois que tu vas réinitialisé à zero , a chaque fois ?
                                                            Non ,tu dois savoir quel sont tes données persistante ( le Perso , le HUD , quelque autre data) , et ceux que tu vas changé souvent (map , ennemi).
                                                            En soit , c'est pas ultra complexe , mais c'est pas forcément intéressant de se mettre ce genre de contrainte.
                                                            (sauf comme dis ,si tu as quelque Mo en RAM , mais c'est pas ta contrainte si ? ).


                                                            Et dans Handmade Hero c'est même pas qu'il n'utilise pas de std::vector, c'est carrément qu'il n'y a aucun tableau dynamique, c'est à dire aucune structure permettant de push_back à gogo. Alors qu'on pourrait se dire à première vue que c'est impossible de s'en passer, mais quand on sait organiser son code comme le fait Casey, on se rend compte que dans la plupart des cas on peut s'en passer.

                                                            C'est là que tu manque d'expérience, dans beaucoup d'autre cas, tu ne pourra pas t'en passer d'un allocateur générique.
                                                            Pas tout le monde fait des jeux 2D , en 8 ans  et qui a peine plus complexe qu'un Mario :)
                                                            Et non tu ne peux pas organiser toujours ton code pour qu'un allocateur LIFO marche...
                                                            (non parce que je m'étais posé cette question hein , et effectivement c'est pas possible )

                                                            avec la façon de Casey... (qui n'est d'ailleurs pas SA façon, plein d'autres gens font ce genre de choses, y compris sur de gros moteurs (cf. OurMachinery que j'ai linké plus haut) donc ça doit être un minimum viable comme méthode quand même)

                                                            Alors ça fait 1000 fois que tu cite Casey ,je sais pas envoie lui une lettre d'amour , mais tu pourrais arrêter de le citer à chaque fois ?
                                                            Surtout que c'est pas sa méthode ,il n'a pas réinventé l'eau chaude.
                                                            Donc je me répète , la méthode est viable, elle est connu depuis 30 ans ,et elle ne résout pas tout les solutions , elle a des contraintes , c'est bon , tu as compris ?
                                                            (et je doute que OurMachinery font que ce type d'allocation ... )

                                                            -
                                                            Edité par HelbaSama 29 mai 2022 à 15:54:12

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                              29 mai 2022 à 16:00:17

                                                              JadeSalina a écrit:

                                                              Oui mais j'avais pas dit explicitement qu'on ne pouvait pas changer l'allocateur, j'ai laissé sous entendre qu'en déclarant des std::vector et autre n'importe où ça ne permettait pas de faire comme Casey à savoir d'allouer d'une façon déterministe à partir d'un bloc préalloué.

                                                              Et tu te plante royalement sur toute la ligne:

                                                              Primo, parce que si tu n'a rien à faire sur un projet pendant deux jours, c'est qu'il est tant de passer à autre chose

                                                              Secundo, parce que, si tu te dis qu'il est temps d'améliorer les perfs, ce n'est pas en travaillant à  l'aveuglette que tu y arrivera, mais bien en faisant tourner des outils qui t'indiqueront les endroits de ton code où tu perds le plus de temps, et en commençant par améliorer ces endroits en priorité.   L'allocation de mémoire n'est surement pas la première chose qui viendra dans la liste

                                                              Tertio car, dés le moment où tu fournis une classe qui expose une fonction allocate et deallocate, en remplacement de std::allocator, tu y mets strictement ce que  tu veux. y compris n'importe quel système te permettant de n'utiliser qu'une quantité prédéterminée et pré allouée de mémoire.

                                                              JadeSalina a écrit:

                                                              Par contre dans le cas particulier de la mémoire, déjà on peut pas faire grand chose sans, vous en conviendrez,

                                                              On ne peut pas faire grand chose pour obtenir d'avantage de mémoire, ca, je te l'accorde.

                                                              Cela ne veut pas dire qu'il n'y ait rien à faire, bien au contraire: Cela veut dire qu'il est sans doute plus que  largement temps de sauvegarder le travail qui a été produit jusqu'à présent pour éviter de le perdre.

                                                              Parce que, si ton programme plante avant d'avoir donné l'occasion de le faire, je connais de PC ou des écrans qui vont passer par la fenêtre!

                                                              Or, déréférencer un pointeur sans s'être assuré au préalable qu'il est valide est encore le meilleur moyen de faire planter ton programme sans donner l'occasion de sauvegarder le travail.

                                                              JadeSalina a écrit:

                                                              et qui peut également fail à tout instant, plutôt qu'un allocateur dont l'allocation est "instantanée" et qui pourrait en plus ne jamais fail

                                                              Rassures moi, ta mauvaise fois, elle est simulée là???

                                                              Car tu viens de comparer l'allocateur générique "qui pourrait échouer à n'importe quel moment" à un allocateur perso "qui pourrait ne jamais échouer" ??? Est-ce bien cela que je lis?

                                                              Sous entendu: je crois, non, je suis convaincue que l'allocateur générique va échouer mais que le mien n'échouera jamais?

                                                              Veux tu qu'on essaye de faire échouer ton allocateur manuel? je n'aurai pas besoin de plus d'écrire du code pendant plus de trois minutes avant d'avoir un programme qui le fera échouer. Et si tu code ton allocateur avec les pieds comme tu l'as fait pour celui que tu nous as montré en exemple, je te garanti le crash de l'application, sans espoir de récupérer quoi que ce soit.

                                                              Et pour répondre à ta question: oui, bien sur que l'on préfère l'allocateur générique, et tu sais pourquoi?

                                                              Parce que lui, au moins, il a le bon gout de ne pas faire planter le programme sur l'instruction qui suit son utilisation: s'il il échoue, il lance une exception, ce qui nous permet soit de laisser planter le programme, soit de prendre les mesures qui nous permettront de sauvegarder le maximum de chose avant de laisser l'exception arrêter le programme

                                                              JadeSalina a écrit:

                                                              Et si la mémoire avait déjà été effectivement allouée précédemment (soit au début du programme, soit dans une frame précédente), il n'y aura pas d'allocation réelle à faire ici. C'est ce qu'essaye de faire malloc dans la mesure du possible, mais quand on fait des trucs sérieux, on ne fait pas du "best effort", on veut garantir que ça va marcher.

                                                              Est-ce de la mauvaise foi ou n'as tu simplement rien compris?

                                                              Parce que comparer une fonction (malloc) qui fait un appel système pour demander de la mémoire au système d'exploitation à une fonction interne qui cherche de la mémoire libre dans un bloc disponible, c'est comparer la terre avec la lune.

                                                              Si malloc échoue à te donner de la mémoire, c'est que le système d'exploitation aura décidé que "maintenant, ca suffit, car je dois garder de la mémoire pour le reste", à moins que l'ensemble de ton système n'ai déjà commencé à ramer depuis cinq minutes par manque de mémoire pour l'ensemble des applications.

                                                              Alors que, si tout le bloc de mémoire préallouée de ton allocateur est utilisé, ton allocateur n'aura de toutes manière que deux possibilités:

                                                              • soit demander du rabe au système d'exploitation (et devine de quellle manière? )
                                                              • soit te dire que "désolé, je ne peux pas te donner ce que tu  me demande"

                                                              Autrement dit, ta soit disant garantie que ca va marcher n'est que poudre aux yeux, car tu ne peux l'avoir que dans la limite de la taille du bloc préalloué la première fois

                                                              Encore une fois, tu as cette manie de croire que tout ce que tu développe ne peut s'exécuter que correctement, parce que ton code fait loi. Il faut absolument que  tu comprenne que, quoi que tu fasses, ton code ne pourra fonctionner correctement que si l'ensemble du système auquel il se rattache lui permet de le faire, ce qui implique:

                                                              • les ressources propres à l'ordinateur sur lequel ton code s'exécute
                                                              • le système d'exploitation de l'ordinateur en question
                                                              • l'ensemble des sources de données externes (clavier, site web, base de données, fichiers, ...) auxquels ton programme fait appel
                                                              • l'utilisateur, auquel tu ne pourra que rarement éviter de faire appel pour obtenir certaines informations

                                                              S'il n'y a ne serait ce qu'un seul de ces éléments qui "ne suit pas le rythme", qui ne peut pas fournir les ressources dont ton programme a besoin, ou qui fournit des informations incohérentes, ton programme ne peut pas fonctionner comme le code lui dit de le faire.

                                                              Et tant que tu n'auras pas compris qu'une telle situation arrive systématiquement, tu ne pourras pas faire un programme correct.

                                                              • 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

                                                              Il ne faut pas écouter tout ce qu'on dit

                                                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                              • Editeur
                                                              • Markdown