Clic sur la falaise â CTO de Cratus (capteurs IoT pour l'amĂ©lioration des processus), fondateur et co-fondateur de plusieurs startups (dont Rocket Realtime School, Neurensic et H2O.ai) avec plusieurs sorties rĂ©ussies. Cliff a Ă©crit son premier compilateur Ă 15 ans (Pascal pour le TRS Z-80) ! Il est surtout connu pour ses travaux sur C2 en Java (le Sea of ââââNodes IR). Ce compilateur a montrĂ© au monde que JIT pouvait produire du code de haute qualitĂ©, ce qui a Ă©tĂ© l'un des facteurs de l'Ă©mergence de Java comme l'une des principales plates-formes logicielles modernes. Ensuite, Cliff a aidĂ© Azul Systems Ă construire un ordinateur central Ă 864 cĆurs avec un logiciel Java pur prenant en charge les pauses GC sur un tas de 500 Go en 10 millisecondes. En gĂ©nĂ©ral, Cliff a rĂ©ussi Ă travailler sur tous les aspects de la JVM.
Cet Habrapost est une excellente interview de Cliff. Nous parlerons des sujets suivants :
- Transition vers des optimisations de bas niveau
- Comment faire une grande refactorisation
- ModÚle de coût
- Formation d'optimisation de bas niveau
- Exemples pratiques dâamĂ©lioration des performances
- Pourquoi créer votre propre langage de programmation
- CarriĂšre dâingĂ©nieur de performance
- Défis techniques
- Un peu sur l'allocation des registres et les multicĆurs
- Le plus grand défi de la vie
L'entretien est mené par :
- Andreï Satarine d'Amazon Web Services. Au cours de sa carriÚre, il a réussi à travailler sur des projets complÚtement différents : il a testé la base de données distribuée NewSQL dans Yandex, un systÚme de détection de cloud dans Kaspersky Lab, un jeu multijoueur dans Mail.ru et un service de calcul des prix des changes chez Deutsche Bank. Intéressé par les tests de systÚmes backend et distribués à grande échelle.
- Vladimir Sitnikov de Netcracker. Dix ans de travail sur les performances et l'évolutivité de NetCracker OS, logiciel utilisé par les opérateurs télécoms pour automatiser les processus de gestion des réseaux et des équipements réseaux. Intéressé par les problÚmes de performances de Java et Oracle Database. Auteur de plus d'une douzaine d'améliorations de performances dans le pilote officiel PostgreSQL JDBC.
Transition vers des optimisations de bas niveau
Andrew: Vous ĂȘtes un grand nom dans le monde de la compilation JIT, de Java et du travail de performance en gĂ©nĂ©ral, n'est-ce pas ?
falaise: C'est comme ça!
Andrew: Commençons par quelques questions générales sur le travail de performance. Que pensez-vous du choix entre des optimisations de haut niveau et de bas niveau, comme travailler au niveau du CPU ?
falaise: Oui, tout est simple ici. Le code le plus rapide est celui qui ne s'exĂ©cute jamais. Il faut donc toujours partir d'un niveau Ă©levĂ©, travailler sur des algorithmes. Une meilleure notation O battra une moins bonne notation O, Ă moins que des constantes suffisamment grandes n'interviennent. Les choses de bas niveau durent. En rĂšgle gĂ©nĂ©rale, si vous avez suffisamment bien optimisĂ© le reste de votre pile et qu'il reste encore des Ă©lĂ©ments intĂ©ressants, c'est un niveau faible. Mais comment partir dâun haut niveau ? Comment savez-vous quâun travail de haut niveau suffisant a Ă©tĂ© effectuĂ© ? Eh bien... pas question. Il nâexiste pas de recettes toutes faites. Vous devez comprendre le problĂšme, dĂ©cider de ce que vous allez faire (afin de ne pas prendre de mesures inutiles Ă l'avenir), puis dĂ©couvrir le profileur qui peut dire quelque chose d'utile. Ă un moment donnĂ©, vous rĂ©alisez vous-mĂȘme que vous vous ĂȘtes dĂ©barrassĂ© des choses inutiles et quâil est temps de procĂ©der Ă quelques ajustements de bas niveau. Il sâagit certainement dâun type dâart particulier. Beaucoup de gens font des choses inutiles, mais avancent si vite quâils nâont pas le temps de se soucier de la productivitĂ©. Mais cela jusquâĂ ce que la question se pose sans ambages. Habituellement, 99 % du temps, personne ne se soucie de ce que je fais, jusqu'au moment oĂč une chose importante arrive sur le chemin critique et dont personne ne se soucie. Et lĂ , tout le monde commence Ă vous harceler en vous demandant « pourquoi cela nâa pas fonctionnĂ© parfaitement dĂšs le dĂ©but ». En gĂ©nĂ©ral, il y a toujours quelque chose Ă amĂ©liorer en termes de performances. Mais 99 % du temps, vous nâavez aucune piste ! Vous essayez simplement de faire fonctionner quelque chose et, ce faisant, vous dĂ©couvrez ce qui est important. Vous ne pouvez jamais savoir Ă lâavance que cette piĂšce doit ĂȘtre parfaite, donc en fait, vous devez ĂȘtre parfait en tout. Mais câest impossible et vous ne le faites pas. Il y a toujours beaucoup de choses Ă rĂ©parer, et c'est tout Ă fait normal.
Comment faire une grande refactorisation
Andrew: Comment travaillez-vous sur une performance ? Il sâagit dâun problĂšme transversal. Par exemple, avez-vous dĂ©jĂ dĂ» travailler sur des problĂšmes rĂ©sultant de lâintersection de nombreuses fonctionnalitĂ©s existantes ?
falaise: J'essaie de l'Ă©viter. Si je sais que les performances seront un problĂšme, j'y rĂ©flĂ©chis avant de commencer Ă coder, notamment avec les structures de donnĂ©es. Mais souvent, on dĂ©couvre tout cela trĂšs tard. Et puis il faut prendre des mesures extrĂȘmes et faire ce que jâappelle « réécrire et conquĂ©rir » : il faut sâemparer dâun morceau suffisamment gros. Une partie du code devra encore ĂȘtre réécrite en raison de problĂšmes de performances ou autre. Quelle que soit la raison de la réécriture du code, il est presque toujours prĂ©fĂ©rable de réécrire un morceau plus gros qu'un morceau plus petit. Ă ce moment-lĂ , tout le monde commence Ă trembler de peur : « oh mon Dieu, tu ne peux pas toucher Ă autant de code ! » Mais en fait, cette approche fonctionne presque toujours bien mieux. Vous devez immĂ©diatement vous attaquer Ă un gros problĂšme, tracer un grand cercle autour et dire : je vais tout réécrire Ă l'intĂ©rieur du cercle. La bordure est beaucoup plus petite que le contenu qui doit ĂȘtre remplacĂ©. Et si une telle dĂ©limitation des limites vous permet de faire parfaitement le travail Ă l'intĂ©rieur, vos mains sont libres, faites ce que vous voulez. Une fois que vous avez compris le problĂšme, le processus de réécriture est beaucoup plus facile, alors prenez-y une grosse bouchĂ©e !
Dans le mĂȘme temps, lorsque vous effectuez une réécriture importante et rĂ©alisez que les performances vont ĂȘtre un problĂšme, vous pouvez immĂ©diatement commencer Ă vous en inquiĂ©ter. Cela se traduit gĂ©nĂ©ralement par des choses simples comme « ne copiez pas les donnĂ©es, gĂ©rez les donnĂ©es aussi simplement que possible, rĂ©duisez-les ». Dans les réécritures volumineuses, il existe des moyens standard dâamĂ©liorer les performances. Et ils tournent presque toujours autour des donnĂ©es.
ModÚle de coût
Andrew: Dans l'un des podcasts, vous avez parlé des modÚles de coûts dans le contexte de la productivité. Pouvez-vous expliquer ce que vous entendiez par là ?
falaise: Certainement. Je suis nĂ© Ă une Ă©poque oĂč les performances du processeur Ă©taient extrĂȘmement importantes. Et cette Ă©poque revient Ă nouveau - le destin n'est pas sans ironie. J'ai commencĂ© Ă vivre Ă l'Ă©poque des machines Ă huit bits ; mon premier ordinateur fonctionnait avec 256 octets. Exactement des octets. Tout Ă©tait trĂšs petit. Les instructions devaient ĂȘtre comptĂ©es, et Ă mesure que nous commencions Ă gravir les Ă©chelons de la pile des langages de programmation, les langages en prenaient de plus en plus. Il y avait Assembler, puis Basic, puis C, et C s'occupait de nombreux dĂ©tails, comme l'allocation des registres et la sĂ©lection des instructions. Mais tout y Ă©tait assez clair, et si je faisais un pointeur vers une instance d'une variable, alors j'obtiendrais une charge et le coĂ»t de cette instruction est connu. Le matĂ©riel produit un certain nombre de cycles machine, donc la vitesse d'exĂ©cution de diffĂ©rentes choses peut ĂȘtre calculĂ©e simplement en additionnant toutes les instructions que vous allez exĂ©cuter. Chaque comparaison/test/branche/appel/chargement/magasin pourrait ĂȘtre additionnĂ© et dire : c'est le temps d'exĂ©cution pour vous. Lorsque vous travaillez sur l'amĂ©lioration des performances, vous ferez certainement attention aux nombres qui correspondent aux petits cycles chauds.
Mais dĂšs que vous passez Ă Java, Python et autres choses similaires, vous vous Ă©loignez trĂšs rapidement du matĂ©riel de bas niveau. Quel est le coĂ»t de lâappel dâun getter en Java ? Si JIT dans HotSpot est correct , il se chargera, mais s'il ne l'a pas fait, ce sera un appel de fonction. Puisque l'appel est sur une boucle chaude, il remplacera toutes les autres optimisations de cette boucle. Le coĂ»t rĂ©el sera donc bien plus Ă©levĂ©. Et vous perdez immĂ©diatement la possibilitĂ© de regarder un morceau de code et de comprendre que nous devons l'exĂ©cuter en termes de vitesse d'horloge du processeur, de mĂ©moire et de cache utilisĂ©s. Tout cela ne devient intĂ©ressant que si lâon se met vraiment dans la performance.
Nous nous retrouvons dĂ©sormais dans une situation oĂč les vitesses des processeurs nâont pratiquement pas augmentĂ© depuis une dĂ©cennie. Le bon vieux temps est de retour ! Vous ne pouvez plus compter sur de bonnes performances en monothread. Mais si vous vous lancez soudainement dans lâinformatique parallĂšle, câest incroyablement difficile, tout le monde vous regarde comme James Bond. Ici, des accĂ©lĂ©rations dĂ©cuplĂ©es se produisent gĂ©nĂ©ralement dans des endroits oĂč quelqu'un a gĂąchĂ© quelque chose. La concurrence nĂ©cessite beaucoup de travail. Pour obtenir cette accĂ©lĂ©ration XNUMXx, vous devez comprendre le modĂšle de coĂ»t. Quoi et combien ça coĂ»te ? Et pour ce faire, vous devez comprendre comment la languette sâadapte au matĂ©riel sous-jacent.
Martin Thompson a choisi un bon mot pour son blog ! Vous devez comprendre ce que le matĂ©riel va faire, comment il le fera exactement et pourquoi il fait ce qu'il fait en premier lieu. GrĂące Ă cela, il est assez facile de commencer Ă compter les instructions et de dĂ©terminer oĂč va le temps d'exĂ©cution. Si vous n'avez pas la formation appropriĂ©e, vous cherchez simplement un chat noir dans une piĂšce sombre. Je vois tout le temps des gens optimiser leurs performances sans savoir ce qu'ils font. Ils souffrent beaucoup et ne font pas beaucoup de progrĂšs. Et quand je prends le mĂȘme morceau de code, que j'y glisse quelques petits hacks et que j'obtiens une accĂ©lĂ©ration cinq ou dix fois supĂ©rieure, ils me disent : eh bien, ce n'est pas juste, nous savions dĂ©jĂ que vous Ă©tiez meilleur. Incroyable. De quoi je parle... le modĂšle de coĂ»t concerne le type de code que vous Ă©crivez et la vitesse Ă laquelle il s'exĂ©cute en moyenne dans son ensemble.
Andrew: Et comment peux-tu garder un tel volume dans ta tĂȘte ? Est-ce que cela est rĂ©alisĂ© avec plus d'expĂ©rience, ou ? DâoĂč vient une telle expĂ©rience ?
falaise: Eh bien, je nâai pas acquis mon expĂ©rience de la maniĂšre la plus simple. J'ai programmĂ© en Assembly Ă l'Ă©poque oĂč l'on pouvait comprendre chaque instruction. Cela semble stupide, mais depuis, le jeu d'instructions du Z80 est toujours restĂ© dans ma tĂȘte, dans ma mĂ©moire. Je ne me souviens pas des noms des gens une minute aprĂšs avoir parlĂ©, mais je me souviens du code Ă©crit il y a 40 ans. C'est drĂŽle, ça ressemble Ă un syndrome"».
Formation d'optimisation de bas niveau
Andrew: Y a-t-il un moyen plus simple d'entrer ?
falaise: Oui et non. Le matĂ©riel que nous utilisons tous nâa pas beaucoup changĂ© au fil du temps. Tout le monde utilise x86, Ă l'exception des smartphones Arm. Si vous ne faites pas une sorte d'intĂ©gration hardcore, vous faites la mĂȘme chose. D'accord, ensuite. Les instructions nâont pas non plus changĂ© depuis des siĂšcles. Vous devez aller Ă©crire quelque chose dans Assembly. Pas grand-chose, mais suffisamment pour commencer Ă comprendre. Vous souriez, mais je parle tout Ă fait sĂ©rieusement. Vous devez comprendre la correspondance entre le langage et le matĂ©riel. AprĂšs cela, vous devez Ă©crire un peu et crĂ©er un petit compilateur jouet pour un petit langage jouet. S'apparentant Ă un jouet signifie qu'il doit ĂȘtre fabriquĂ© dans un dĂ©lai raisonnable. Cela peut ĂȘtre super simple, mais cela doit gĂ©nĂ©rer des instructions. Le fait de gĂ©nĂ©rer une instruction vous aidera Ă comprendre le modĂšle de coĂ»t du pont entre le code de haut niveau que tout le monde Ă©crit et le code machine qui s'exĂ©cute sur le matĂ©riel. Cette correspondance sera gravĂ©e dans le cerveau au moment oĂč le compilateur sera Ă©crit. MĂȘme le compilateur le plus simple. AprĂšs cela, vous pouvez commencer Ă examiner Java et le fait que son gouffre sĂ©mantique est beaucoup plus profond et qu'il est beaucoup plus difficile de construire des ponts dessus. En Java, il est beaucoup plus difficile de comprendre si notre pont s'est avĂ©rĂ© bon ou mauvais, ce qui le fera s'effondrer et ce qui ne le fera pas. Mais vous avez besoin d'une sorte de point de dĂ©part oĂč vous regardez le code et comprenez : « ouais, ce getter devrait ĂȘtre intĂ©grĂ© Ă chaque fois. » Et puis il s'avĂšre que cela se produit parfois, sauf dans le cas oĂč la mĂ©thode devient trop volumineuse et oĂč le JIT commence Ă tout intĂ©grer. Les performances de ces lieux peuvent ĂȘtre prĂ©dites instantanĂ©ment. Habituellement, les getters fonctionnent bien, mais ensuite vous regardez les grandes boucles chaudes et vous rĂ©alisez qu'il y a des appels de fonction qui circulent lĂ -bas qui ne savent pas ce qu'ils font. C'est le problĂšme de l'utilisation gĂ©nĂ©ralisĂ©e des getters, la raison pour laquelle ils ne sont pas intĂ©grĂ©s est qu'il n'est pas clair s'ils sont des getters. Si vous avez une trĂšs petite base de code, vous pouvez simplement vous en souvenir et dire ensuite : ceci est un getter, et ceci est un setter. Dans une grande base de code, chaque fonction vit sa propre histoire, qui, en gĂ©nĂ©ral, n'est connue de personne. Le profileur dit que nous avons perdu 24% du temps sur certaines boucles et pour comprendre ce que fait cette boucle, nous devons examiner chaque fonction qu'elle contient. Il est impossible de comprendre cela sans Ă©tudier la fonction, ce qui ralentit considĂ©rablement le processus de comprĂ©hension. C'est pourquoi je n'utilise pas de getters et de setters, j'ai atteint un nouveau niveau !
OĂč trouver le modĂšle de coĂ»t ? Eh bien, vous pouvez lire quelque chose, bien sĂ»r... Mais je pense que la meilleure façon est d'agir. CrĂ©er un petit compilateur sera le meilleur moyen de comprendre le modĂšle de coĂ»t et de l'adapter Ă votre propre tĂȘte. Un petit compilateur qui conviendrait Ă la programmation d'un micro-ondes est une tĂąche pour un dĂ©butant. Eh bien, je veux dire, si vous avez dĂ©jĂ des compĂ©tences en programmation, cela devrait suffire. Toutes ces choses comme analyser une chaĂźne que vous avez comme une sorte d'expression algĂ©brique, en extraire des instructions pour des opĂ©rations mathĂ©matiques dans le bon ordre, prendre les valeurs correctes des registres - tout cela se fait en mĂȘme temps. Et pendant que vous le ferez, cela sâimprimera dans votre cerveau. Je pense que tout le monde sait ce que fait un compilateur. Et cela donnera une comprĂ©hension du modĂšle de coĂ»t.
Exemples pratiques dâamĂ©lioration des performances
Andrew: à quoi d'autre devez-vous faire attention lorsque vous travaillez sur la productivité ?
falaise: Structures de donnĂ©es. D'ailleurs, oui, je n'ai pas donnĂ© ces cours depuis longtemps... . C'Ă©tait amusant, mais cela demandait beaucoup d'efforts, et j'ai aussi une vie ! D'ACCORD. Ainsi, dans l'un des cours les plus importants et les plus intĂ©ressants, « OĂč vont vos performances », j'ai donnĂ© un exemple aux Ă©tudiants : deux gigaoctets et demi de donnĂ©es fintech ont Ă©tĂ© lus Ă partir d'un fichier CSV, puis ils ont dĂ» calculer le nombre de produits vendus. . DonnĂ©es rĂ©guliĂšres sur le marchĂ© des ticks. Paquets UDP convertis au format texte depuis les annĂ©es 70. Chicago Mercantile Exchange â toutes sortes de choses comme le beurre, le maĂŻs, le soja, des choses comme ça. Il fallait compter ces produits, le nombre de transactions, le volume moyen de mouvement de fonds et de biens, etc. C'est un calcul commercial assez simple : trouvez le code du produit (soit 1 Ă 2 caractĂšres dans la table de hachage), obtenez le montant, ajoutez-le Ă l'un des ensembles d'Ă©changes, ajoutez du volume, ajoutez de la valeur et quelques autres choses. MathĂ©matiques trĂšs simples. L'implĂ©mentation du jouet a Ă©tĂ© trĂšs simple : tout est dans un fichier, je lis le fichier et je le parcours, divisant les enregistrements individuels en chaĂźnes Java, recherchant les Ă©lĂ©ments nĂ©cessaires et les additionnant selon les mathĂ©matiques dĂ©crites ci-dessus. Et cela fonctionne Ă faible vitesse.
Avec cette approche, ce qui se passe est Ă©vident, et le calcul parallĂšle n'aidera pas, n'est-ce pas ? Il sâavĂšre quâune multiplication par cinq des performances peut ĂȘtre obtenue simplement en choisissant les bonnes structures de donnĂ©es. Et cela surprend mĂȘme les programmeurs expĂ©rimentĂ©s ! Dans mon cas particulier, l'astuce Ă©tait de ne pas effectuer d'allocation de mĂ©moire dans une boucle chaude. Eh bien, ce n'est pas toute la vĂ©ritĂ©, mais en gĂ©nĂ©ral, vous ne devriez pas mettre en surbrillance « une fois dans X » lorsque X est suffisamment grand. Lorsque X fait deux gigaoctets et demi, vous ne devriez rien allouer « une fois par lettre », ou « une fois par ligne », ou « une fois par champ », quelque chose comme ça. C'est lĂ que l'on passe du temps. Comment est-ce que ça marche ? Imagine que je passe un appel String.split() ou BufferedReader.readLine(). Readline crĂ©e une chaĂźne Ă partir d'un ensemble d'octets transitant par le rĂ©seau, une fois pour chaque ligne, pour chacune des centaines de millions de lignes. Je prends cette ligne, l'analyse et la jette. Pourquoi est-ce que je le jette - eh bien, je l'ai dĂ©jĂ traitĂ©, c'est tout. Ainsi, pour chaque octet lu Ă partir de ces 2.7G, deux caractĂšres seront Ă©crits dans la ligne, c'est-Ă -dire dĂ©jĂ 5.4G, et je n'en ai plus besoin pour rien d'autre, ils sont donc jetĂ©s. Si vous regardez la bande passante mĂ©moire, nous chargeons 2.7G qui traversent la mĂ©moire et le bus mĂ©moire du processeur, puis deux fois plus sont envoyĂ©s Ă la ligne situĂ©e en mĂ©moire, et tout cela s'effiloche lorsque chaque nouvelle ligne est créée. Mais jâai besoin de le lire, le matĂ©riel le lit, mĂȘme si tout sâeffiloche par la suite. Et je dois l'Ă©crire car j'ai créé une ligne et les caches sont pleins - le cache ne peut pas accueillir 2.7G. Ainsi, pour chaque octet que je lis, je lis deux octets supplĂ©mentaires et j'Ă©cris deux octets supplĂ©mentaires, et au final, ils ont un rapport de 4:1 - dans ce rapport, nous gaspillons de la bande passante mĂ©moire. Et puis il s'avĂšre que si je le fais String.split() â ce n'est pas la derniĂšre fois que je fais ça, il peut y avoir encore 6 Ă 7 champs Ă l'intĂ©rieur. Ainsi, le code classique consistant Ă lire CSV puis Ă analyser les chaĂźnes entraĂźne un gaspillage de bande passante mĂ©moire d'environ 14: 1 par rapport Ă ce que vous aimeriez rĂ©ellement avoir. Si vous jetez ces sĂ©lections, vous pouvez obtenir une accĂ©lĂ©ration quintuplĂ©e.
Et ce n'est pas si difficile. Si vous regardez le code sous le bon angle, tout devient assez simple une fois que vous rĂ©alisez le problĂšme. Vous ne devriez pas arrĂȘter complĂštement d'allouer de la mĂ©moire : le seul problĂšme est que vous allouez quelque chose et il meurt immĂ©diatement, et en cours de route, il brĂ»le une ressource importante, qui dans ce cas est la bande passante mĂ©moire. Et tout cela se traduit par une baisse de productivitĂ©. Sur x86, vous devez gĂ©nĂ©ralement graver activement les cycles du processeur, mais ici, vous avez brĂ»lĂ© toute la mĂ©moire beaucoup plus tĂŽt. La solution consiste Ă rĂ©duire la quantitĂ© de rejets.
L'autre partie du problĂšme est que si vous exĂ©cutez le profileur lorsque la bande de mĂ©moire est Ă©puisĂ©e, juste au moment oĂč cela se produit, vous attendez gĂ©nĂ©ralement que le cache revienne parce qu'il est plein de dĂ©chets que vous venez de produire, toutes ces lignes. Par consĂ©quent, chaque opĂ©ration de chargement ou de stockage devient lente, car elle entraĂźne des Ă©checs de cache - l'ensemble du cache est devenu lent, attendant que les dĂ©chets le quittent. Par consĂ©quent, le profileur affichera simplement un bruit alĂ©atoire chaud rĂ©parti tout au long de la boucle entiĂšre - il n'y aura pas d'instruction chaude ou d'endroit sĂ©parĂ© dans le code. Seulement du bruit. Et si vous regardez les cycles GC, ils sont tous de jeune gĂ©nĂ©ration et ultra rapides â microsecondes ou millisecondes maximum. AprĂšs tout, toute cette mĂ©moire meurt instantanĂ©ment. Vous allouez des milliards de gigaoctets, et il les coupe, et les coupe, et les coupe encore. Tout cela arrive trĂšs vite. Il s'avĂšre qu'il existe des cycles GC bon marchĂ©, un bruit chaud tout au long du cycle, mais nous voulons obtenir une accĂ©lĂ©ration 5x. Ă ce moment-lĂ , quelque chose devrait se fermer dans votre tĂȘte et sonner : « pourquoi ça ?! » Le dĂ©passement de bande mĂ©moire n'est pas affichĂ© dans le dĂ©bogueur classique, vous devez exĂ©cuter le dĂ©bogueur du compteur de performances matĂ©rielles et le constater vous-mĂȘme et directement. Mais cela ne peut ĂȘtre directement suspectĂ© Ă partir de ces trois symptĂŽmes. Le troisiĂšme symptĂŽme est que lorsque vous regardez ce que vous mettez en surbrillance, demandez au profileur, et il rĂ©pond : « Vous avez créé un milliard de lignes, mais le GC a fonctionnĂ© gratuitement. DĂšs que cela se produit, vous rĂ©alisez que vous avez créé trop dâobjets et brĂ»lĂ© tout le chemin de la mĂ©moire. Il existe un moyen de comprendre cela, mais ce n'est pas Ă©vident.
Le problĂšme rĂ©side dans la structure des donnĂ©es : la structure nue sous-jacente Ă tout ce qui se passe, elle est trop grande, elle fait 2.7 Go sur le disque, donc faire une copie de cette chose est trĂšs indĂ©sirable - vous voulez la charger immĂ©diatement Ă partir du tampon d'octets du rĂ©seau dans les registres, afin de ne pas lire-Ă©crire sur la ligne cinq fois. Malheureusement, Java ne vous propose pas par dĂ©faut une telle bibliothĂšque dans le cadre du JDK. Mais c'est trivial, non ? Essentiellement, il s'agit de 5 Ă 10 lignes de code qui seront utilisĂ©es pour implĂ©menter votre propre chargeur de chaĂźne tamponnĂ©, qui rĂ©pĂšte le comportement de la classe de chaĂźne, tout en Ă©tant un wrapper autour du tampon d'octets sous-jacent. En consĂ©quence, il s'avĂšre que vous travaillez presque comme avec des chaĂźnes, mais en fait les pointeurs vers le tampon s'y dĂ©placent, et les octets bruts ne sont copiĂ©s nulle part, et donc les mĂȘmes tampons sont rĂ©utilisĂ©s encore et encore, et le systĂšme d'exploitation est heureux de prendre en charge les choses pour lesquelles il a Ă©tĂ© conçu, comme la double mise en mĂ©moire tampon cachĂ©e de ces tampons d'octets, et vous n'ĂȘtes plus en train de parcourir un flux infini de donnĂ©es inutiles. Au fait, comprenez-vous qu'en travaillant avec GC, il est garanti que chaque allocation de mĂ©moire ne sera pas visible par le processeur aprĂšs le dernier cycle GC ? Par consĂ©quent, tout cela ne peut pas ĂȘtre dans le cache, et alors un Ă©chec garanti Ă 100 % se produit. Lorsque vous travaillez avec un pointeur, sur x86, soustraire un registre de la mĂ©moire prend 1 Ă 2 cycles d'horloge, et dĂšs que cela se produit, vous payez, payez, payez, car toute la mĂ©moire est allumĂ©e â et c'est le coĂ»t de l'allocation de mĂ©moire. Valeur rĂ©elle.
En dâautres termes, les structures de donnĂ©es sont la chose la plus difficile Ă modifier. Et une fois que vous rĂ©alisez que vous avez choisi la mauvaise structure de donnĂ©es qui nuira aux performances plus tard, il y a gĂ©nĂ©ralement beaucoup de travail Ă faire, mais si vous ne le faites pas, les choses empireront. Tout dâabord, vous devez penser aux structures de donnĂ©es, câest important. Le coĂ»t principal ici repose sur les grosses structures de donnĂ©es, qui commencent Ă ĂȘtre utilisĂ©es dans le style de « J'ai copiĂ© la structure de donnĂ©es X dans la structure de donnĂ©es Y parce que j'aime mieux la forme de Y ». Mais l'opĂ©ration de copie (qui semble bon marchĂ©) gaspille en rĂ©alitĂ© de la bande passante mĂ©moire et c'est lĂ que tout le temps d'exĂ©cution perdu est enterrĂ©. Si j'ai une chaĂźne gĂ©ante de JSON et que je veux la transformer en une arborescence DOM structurĂ©e de POJO ou quelque chose comme ça, l'opĂ©ration d'analyse de cette chaĂźne et de construction du POJO, puis d'accĂ©der Ă nouveau au POJO plus tard, entraĂźnera des coĂ»ts inutiles - c'est pas cher. Sauf si vous courez autour des POJO beaucoup plus souvent que sur une chaĂźne. Ă la place, vous pouvez essayer de dĂ©crypter la chaĂźne et d'en extraire uniquement ce dont vous avez besoin, sans la transformer en POJO. Si tout cela se produit sur un chemin Ă partir duquel des performances maximales sont requises, pas de POJO pour vous, vous devez d'une maniĂšre ou d'une autre creuser directement dans la ligne.
Pourquoi créer votre propre langage de programmation
Andrew: Vous avez dit que pour comprendre le modÚle de coût, il fallait écrire son propre petit langage...
falaise: Pas un langage, mais un compilateur. Un langage et un compilateur sont deux choses diffĂ©rentes. La diffĂ©rence la plus importante est dans votre tĂȘte.
Andrew: Au fait, pour autant que je sache, vous expérimentez la création de vos propres langages. Pour quoi?
falaise: Parce que je peux! Je suis semi-retraitĂ©, c'est donc mon passe-temps. J'ai mis en Ćuvre les langues des autres toute ma vie. J'ai Ă©galement beaucoup travaillĂ© sur mon style de codage. Et aussi parce que je vois des problĂšmes dans d'autres langues. Je vois qu'il existe de meilleures façons de faire des choses familiĂšres. Et je les utiliserais. Je suis juste fatiguĂ© de voir des problĂšmes en moi-mĂȘme, en Java, en Python, dans n'importe quel autre langage. J'Ă©cris maintenant en React Native, JavaScript et Elm comme passe-temps qui ne concerne pas la retraite, mais le travail actif. J'Ă©cris Ă©galement en Python et, trĂšs probablement, je continuerai Ă travailler sur l'apprentissage automatique pour les backends Java. Il existe de nombreuses langues populaires et elles possĂšdent toutes des fonctionnalitĂ©s intĂ©ressantes. Chacun est bon Ă sa maniĂšre et vous pouvez essayer de rĂ©unir toutes ces fonctionnalitĂ©s. Donc, j'Ă©tudie des choses qui m'intĂ©ressent, le comportement du langage, en essayant de trouver une sĂ©mantique raisonnable. Et jusqu'Ă prĂ©sent, j'ai rĂ©ussi ! Pour le moment, j'ai du mal avec la sĂ©mantique de la mĂ©moire, car je veux l'avoir comme en C et Java, et obtenir un modĂšle de mĂ©moire et une sĂ©mantique de mĂ©moire solides pour les charges et les magasins. En mĂȘme temps, ayez une infĂ©rence de type automatique comme dans Haskell. Ici, j'essaie de mĂ©langer l'infĂ©rence de type Haskell avec le travail de mĂ©moire en C et Java. Câest ce que je fais depuis 2-3 mois par exemple.
Andrew: Si vous construisez un langage qui prend de meilleurs aspects d'autres langages, pensez-vous que quelqu'un fera le contraire : prendre vos idées et les utiliser ?
falaise: C'est exactement ainsi que de nouvelles langues apparaissent ! Pourquoi Java est-il similaire au C ? Parce que C avait une bonne syntaxe que tout le monde comprenait et que Java s'est inspirĂ© de cette syntaxe, en ajoutant la sĂ©curitĂ© des types, la vĂ©rification des limites des tableaux, le GC, et ils ont Ă©galement amĂ©liorĂ© certaines choses de C. Ils ont ajoutĂ© les leurs. Mais ils ont Ă©tĂ© beaucoup inspirĂ©s, non ? Tout le monde sâappuie sur les Ă©paules des gĂ©ants qui vous ont prĂ©cĂ©dĂ©s : câest ainsi que lâon progresse.
Andrew: Si je comprends bien, votre langue sera en sécurité en mémoire. Avez-vous pensé à implémenter quelque chose comme un vérificateur d'emprunt de Rust ? L'avez-vous regardé, que pensez-vous de lui ?
falaise: Eh bien, j'Ă©cris du C depuis des lustres, avec tout ce malloc et gratuit, et je gĂšre manuellement la durĂ©e de vie. Vous savez, 90 Ă 95 % de la durĂ©e de vie contrĂŽlĂ©e manuellement a la mĂȘme structure. Et c'est trĂšs, trĂšs pĂ©nible de le faire manuellement. J'aimerais que le compilateur vous dise simplement ce qui s'y passe et ce que vous avez rĂ©alisĂ© grĂące Ă vos actions. Pour certaines choses, le vĂ©rificateur d'emprunt le fait immĂ©diatement. Et il devrait afficher automatiquement les informations, tout comprendre et ne mĂȘme pas me charger de prĂ©senter cette comprĂ©hension. Il doit effectuer au moins une analyse d'Ă©chappement locale, et seulement en cas d'Ă©chec, il doit alors ajouter des annotations de type qui dĂ©criront la durĂ©e de vie - et un tel schĂ©ma est beaucoup plus complexe qu'un vĂ©rificateur d'emprunt, ou mĂȘme n'importe quel vĂ©rificateur de mĂ©moire existant. Le choix entre « tout va bien » et « je ne comprends rien » - non, il doit y avoir quelque chose de mieux.
Donc, en tant que personne ayant Ă©crit beaucoup de code en C, je pense que la prise en charge du contrĂŽle automatique de la durĂ©e de vie est la chose la plus importante. J'en ai Ă©galement marre de la quantitĂ© de mĂ©moire utilisĂ©e par Java et le principal reproche concerne le GC. Lorsque vous allouez de la mĂ©moire en Java, vous ne rĂ©cupĂ©rerez pas la mĂ©moire qui Ă©tait locale lors du dernier cycle GC. Ce n'est pas le cas dans les langages dotĂ©s d'une gestion de la mĂ©moire plus prĂ©cise. Si vous appelez malloc, vous obtenez immĂ©diatement la mĂ©moire qui vient habituellement d'ĂȘtre utilisĂ©e. Habituellement, vous effectuez des tĂąches temporaires avec la mĂ©moire et la restituez immĂ©diatement. Et il retourne immĂ©diatement au pool malloc, et le cycle malloc suivant le retire Ă nouveau. Par consĂ©quent, lâutilisation rĂ©elle de la mĂ©moire est rĂ©duite Ă lâensemble des objets vivants Ă un instant donnĂ©, plus les fuites. Et si tout ne fuit pas de maniĂšre totalement indĂ©cente, lâessentiel de la mĂ©moire finit dans les caches et le processeur, et cela fonctionne rapidement. Mais nĂ©cessite beaucoup de gestion manuelle de la mĂ©moire avec malloc et free appelĂ©s dans le bon ordre, au bon endroit. Rust peut gĂ©rer cela correctement tout seul et, dans de nombreux cas, offrir des performances encore meilleures, puisque la consommation de mĂ©moire est limitĂ©e au calcul en cours - au lieu d'attendre le prochain cycle GC pour libĂ©rer de la mĂ©moire. En consĂ©quence, nous avons obtenu un moyen trĂšs intĂ©ressant dâamĂ©liorer les performances. Et assez puissant - je veux dire, j'ai fait de telles choses lors du traitement de donnĂ©es pour la fintech, et cela m'a permis d'obtenir une accĂ©lĂ©ration d'environ cinq fois. C'est un gros coup de pouce, surtout dans un monde oĂč les processeurs ne deviennent pas plus rapides et oĂč nous attendons toujours des amĂ©liorations.
CarriĂšre dâingĂ©nieur de performance
Andrew: J'aimerais aussi poser des questions sur les carriÚres en général. Vous avez pris de l'importance grùce à votre travail JIT chez HotSpot, puis avez rejoint Azul, qui est également une société JVM. Mais nous travaillions déjà davantage sur le matériel que sur le logiciel. Et puis ils sont soudainement passés au Big Data et au Machine Learning, puis à la détection des fraudes. Comment est-ce arrivé? Ce sont des domaines de développement trÚs différents.
falaise: Je programme depuis assez longtemps et j'ai rĂ©ussi Ă suivre beaucoup de cours diffĂ©rents. Et quand les gens disent : « oh, c'est toi qui as fait du JIT pour Java ! », c'est toujours drĂŽle. Mais avant cela, je travaillais sur un clone de PostScript, le langage qu'Apple utilisait autrefois pour ses imprimantes laser. Et avant cela, j'ai fait une implĂ©mentation du langage Forth. Je pense que le thĂšme commun pour moi est le dĂ©veloppement dâoutils. Toute ma vie, j'ai créé des outils avec lesquels d'autres personnes Ă©crivent leurs programmes sympas. Mais j'ai Ă©galement Ă©tĂ© impliquĂ© dans le dĂ©veloppement de systĂšmes d'exploitation, de pilotes, de dĂ©bogueurs au niveau du noyau, de langages pour le dĂ©veloppement de systĂšmes d'exploitation, qui au dĂ©but Ă©taient triviaux, mais qui sont devenus de plus en plus complexes au fil du temps. Mais le sujet principal reste le dĂ©veloppement dâoutils. Une grande partie de ma vie s'est dĂ©roulĂ©e entre Azul et Sun, et c'Ă©tait autour de Java. Mais quand je me suis lancĂ© dans le Big Data et lâapprentissage automatique, jâai remis mon chapeau et jâai dit : « Oh, maintenant nous avons un problĂšme non trivial, et il se passe beaucoup de choses intĂ©ressantes et des gens font des choses. » Câest une excellente voie de dĂ©veloppement Ă suivre.
Oui, j'aime vraiment l'informatique distribuĂ©e. Mon premier emploi Ă©tait en tant qu'Ă©tudiant en C, sur un projet publicitaire. Il s'agissait d'un calcul distribuĂ© sur des puces Zilog Z80 qui collectaient des donnĂ©es pour l'OCR analogique, produites par un vĂ©ritable analyseur analogique. C'Ă©tait un sujet cool et complĂštement fou. Mais il y avait des problĂšmes, une partie n'Ă©tait pas reconnue correctement, donc il fallait prendre une photo et la montrer Ă une personne qui pouvait dĂ©jĂ lire avec ses yeux et rapporter ce qu'elle disait, et donc il y avait des travaux avec des donnĂ©es, et ces travaux avaient leur propre langue. Il y avait un backend qui traitait tout cela - des Z80 fonctionnant en parallĂšle avec des terminaux vt100 fonctionnant - un par personne, et il y avait un modĂšle de programmation parallĂšle sur le Z80. Un Ă©lĂ©ment de mĂ©moire commun partagĂ© par tous les Z80 dans une configuration en Ă©toile ; Le fond de panier Ă©tait Ă©galement partagĂ©, et la moitiĂ© de la RAM Ă©tait partagĂ©e au sein du rĂ©seau, et l'autre moitiĂ© Ă©tait privĂ©e ou allait Ă autre chose. Un systĂšme distribuĂ© parallĂšle significativement complexe avec une mĂ©moire partagĂ©e... semi-partagĂ©e. Quand Ă©tait-ce... Je ne m'en souviens mĂȘme pas, quelque part au milieu des annĂ©es 80. Il y a bien longtemps.
Oui, supposons que 30 ans, câest il y a assez longtemps. Les problĂšmes liĂ©s Ă lâinformatique distribuĂ©e existent depuis assez longtemps ; les gens sont depuis longtemps en guerre contre -groupes. De tels clusters ressemblent Ă ... Par exemple : il y a Ethernet et votre x86 rapide est connectĂ© Ă cet Ethernet, et maintenant vous voulez obtenir une fausse mĂ©moire partagĂ©e, car personne ne pouvait alors faire du codage informatique distribuĂ©, c'Ă©tait trop difficile et donc lĂ Ă©tait une fausse mĂ©moire partagĂ©e avec des pages de mĂ©moire de protection sur x86, et si vous Ă©criviez sur cette page, nous disions aux autres processeurs que s'ils accĂ©daient Ă la mĂȘme mĂ©moire partagĂ©e, elle devrait ĂȘtre chargĂ©e par vous, et donc quelque chose comme un protocole de prise en charge la cohĂ©rence du cache est apparue et un logiciel pour cela. Notion intĂ©ressante. Le vrai problĂšme, bien entendu, Ă©tait autre chose. Tout cela a fonctionnĂ©, mais vous avez rapidement eu des problĂšmes de performances, car personne ne comprenait les modĂšles de performances Ă un niveau suffisamment bon - quels Ă©taient les modĂšles d'accĂšs Ă la mĂ©moire, comment s'assurer que les nĆuds ne se cinglaient pas sans fin, etc.
Ce que j'ai dĂ©couvert dans H2O, c'est que ce sont les dĂ©veloppeurs eux-mĂȘmes qui sont chargĂ©s de dĂ©terminer oĂč le parallĂ©lisme est cachĂ© et oĂč il ne l'est pas. J'ai proposĂ© un modĂšle de codage qui rendait l'Ă©criture de code haute performance facile et simple. Mais Ă©crire du code lent est difficile, cela aura l'air mauvais. Vous devez sĂ©rieusement essayer d'Ă©crire du code lent, vous devrez utiliser des mĂ©thodes non standard. Le code de freinage est visible au premier coup d'Ćil. Par consĂ©quent, vous Ă©crivez gĂ©nĂ©ralement du code qui sâexĂ©cute rapidement, mais vous devez savoir quoi faire dans le cas dâune mĂ©moire partagĂ©e. Tout cela est liĂ© aux grands tableaux et le comportement y est similaire Ă celui des grands tableaux non volatils en Java parallĂšle. Je veux dire, imaginez que deux threads Ă©crivent dans un tableau parallĂšle, l'un d'eux gagne et l'autre, en consĂ©quence, perd, et vous ne savez pas lequel est lequel. S'ils ne sont pas volatils, l'ordre peut ĂȘtre celui que vous voulez - et cela fonctionne trĂšs bien. Les gens se soucient vraiment de l'ordre des opĂ©rations, ils placent les volatiles aux bons endroits et s'attendent Ă des problĂšmes de performances liĂ©s Ă la mĂ©moire aux bons endroits. Sinon, ils Ă©criraient simplement du code sous forme de boucles de 1 Ă N, oĂč N vaut quelques milliards, dans l'espoir que tous les cas complexes deviendront automatiquement parallĂšles - et cela ne fonctionne pas lĂ -bas. Mais dans H2O, ce n'est ni Java ni Scala ; vous pouvez le considĂ©rer comme « Java moins moins » si vous le souhaitez. Il sâagit dâun style de programmation trĂšs clair et similaire Ă lâĂ©criture de code simple en C ou Java avec des boucles et des tableaux. Mais en mĂȘme temps, la mĂ©moire peut ĂȘtre traitĂ©e en tĂ©raoctets. J'utilise toujours H2O. Je l'utilise de temps en temps dans diffĂ©rents projets - et c'est toujours l'appareil le plus rapide, des dizaines de fois plus rapide que ses concurrents. Si vous faites du Big Data avec des donnĂ©es en colonnes, il est trĂšs difficile de battre H2O.
Défis techniques
Andrew: Quel a été votre plus grand défi tout au long de votre carriÚre ?
falaise: Discutons-nous de la partie technique ou non technique du problÚme ? Je dirais que les plus grands défis ne sont pas techniques.
Quant aux dĂ©fis techniques. Je les ai simplement vaincus. Je ne sais mĂȘme pas quel Ă©tait le plus gros, mais il y en avait quelques-uns assez intĂ©ressants qui ont pris pas mal de temps, de lutte mentale. Quand je suis allĂ© chez Sun, j'Ă©tais sĂ»r que je crĂ©erais un compilateur rapide, et un groupe de seniors ont rĂ©pondu que je ne rĂ©ussirais jamais. Mais j'ai suivi ce chemin, j'ai Ă©crit un compilateur dans l'allocateur de registre, et c'Ă©tait assez rapide. C'Ă©tait aussi rapide que le C1 moderne, mais l'allocateur Ă©tait beaucoup plus lent Ă l'Ă©poque, et avec le recul, il s'agissait d'un problĂšme important de structure de donnĂ©es. J'en avais besoin pour Ă©crire un allocateur de registre graphique et je ne comprenais pas le dilemme entre l'expressivitĂ© du code et la vitesse, qui existait Ă cette Ă©poque et Ă©tait trĂšs important. Il s'est avĂ©rĂ© que la structure des donnĂ©es dĂ©passe gĂ©nĂ©ralement la taille du cache sur les x86 de cette Ă©poque, et par consĂ©quent, si je supposais initialement que l'allocateur de registre travaillerait sur 5 Ă 10 % du temps de gigue total, alors en rĂ©alitĂ©, cela s'est avĂ©rĂ© ĂȘtre 50 pourcent.
Au fil du temps, le compilateur est devenu plus propre et plus efficace, a cessĂ© de gĂ©nĂ©rer du code Ă©pouvantable dans un plus grand nombre de cas et les performances ont commencĂ© Ă ressembler de plus en plus Ă celles produites par un compilateur C. Ă moins, bien sĂ»r, que vous Ă©criviez des conneries que mĂȘme C n'accĂ©lĂšre pas . Si vous Ă©crivez du code comme C, vous obtiendrez des performances comme C dans plus de cas. Et plus vous alliez loin, plus vous obteniez du code qui coĂŻncidait asymptotiquement avec le niveau C, l'allocateur de registre commençait Ă ressembler Ă quelque chose de complet... que votre code s'exĂ©cute rapidement ou lentement. J'ai continuĂ© Ă travailler sur l'allocateur pour lui faire faire de meilleures sĂ©lections. Il est devenu de plus en plus lent, mais il a donnĂ© de meilleures performances dans les cas oĂč personne d'autre ne pouvait y faire face. Je pourrais plonger dans un allocateur de registre, y enterrer un mois de travail, et tout Ă coup, tout le code commencerait Ă s'exĂ©cuter 5 % plus rapidement. Cela s'est produit Ă maintes reprises et le rĂ©partiteur de registre est devenu une sorte d'Ćuvre d'art - tout le monde l'a aimĂ© ou dĂ©testĂ©, et les gens de l'acadĂ©mie ont posĂ© des questions sur le thĂšme « pourquoi tout est fait de cette façon », pourquoi pas , et quelle est la diffĂ©rence. La rĂ©ponse est toujours la mĂȘme : un allocateur basĂ© sur la coloration des graphiques et un travail trĂšs minutieux avec le code tampon Ă©quivaut Ă une arme de victoire, la meilleure combinaison que personne ne puisse vaincre. Et câest une chose plutĂŽt peu Ă©vidente. Tout le reste que fait le compilateur lĂ -bas est des choses assez bien Ă©tudiĂ©es, mĂȘme si elles ont Ă©galement Ă©tĂ© portĂ©es au niveau de l'art. J'ai toujours fait des choses censĂ©es transformer le compilateur en une Ćuvre d'art. Mais rien de tout cela nâavait quelque chose dâextraordinaire â Ă lâexception du rĂ©partiteur du registre. L'astuce est d'ĂȘtre prudent sous charge et, si cela se produit (je peux l'expliquer plus en dĂ©tail si vous ĂȘtes intĂ©ressĂ©), cela signifie que vous pouvez vous aligner de maniĂšre plus agressive, sans risquer de tomber sur un problĂšme dans le calendrier des performances. Ă cette Ă©poque, il y avait un tas de compilateurs Ă grande Ă©chelle, ornĂ©s de bibelots et de sifflets, dotĂ©s d'allocateurs de registre, mais personne d'autre ne pouvait le faire.
Le problĂšme est que si vous ajoutez des mĂ©thodes soumises Ă l'inline, en augmentant et en augmentant la zone d'inline, l'ensemble des valeurs utilisĂ©es dĂ©passe instantanĂ©ment le nombre de registres et vous devez les couper. Le niveau critique survient gĂ©nĂ©ralement lorsque l'allocateur abandonne, et qu'un bon candidat pour un dĂ©versement en vaut un autre, vous vendrez des choses gĂ©nĂ©ralement folles. La valeur de l'inline ici est que vous perdez une partie des frais gĂ©nĂ©raux, des frais gĂ©nĂ©raux d'appel et de sauvegarde, vous pouvez voir les valeurs Ă l'intĂ©rieur et les optimiser davantage. Le coĂ»t de l'inline est qu'un grand nombre de valeurs rĂ©elles sont formĂ©es, et si votre allocateur de registre brĂ»le plus que nĂ©cessaire, vous perdez immĂ©diatement. Par consĂ©quent, la plupart des rĂ©partiteurs ont un problĂšme : lorsque lâinline franchit une certaine ligne, tout dans le monde commence Ă ĂȘtre rĂ©duit et la productivitĂ© peut ĂȘtre jetĂ©e dans les toilettes. Ceux qui implĂ©mentent le compilateur ajoutent quelques heuristiques : par exemple, arrĂȘter l'inline, en commençant par une taille suffisamment grande, car les allocations gĂącheront tout. C'est ainsi que se forme un coude dans le graphique de performances - vous inline, inline, les performances augmentent lentement - et puis boum ! â il tombe comme un cric rapide parce que vous avez trop alignĂ©. C'est ainsi que tout fonctionnait avant l'avĂšnement de Java. Java nĂ©cessite beaucoup plus d'inline, j'ai donc dĂ» rendre mon allocateur beaucoup plus agressif pour qu'il se stabilise plutĂŽt que de planter, et si vous en inlinez trop, il commence Ă se rĂ©pandre, mais le moment « plus de dĂ©versement » arrive toujours. Câest une observation intĂ©ressante et elle mâest venue de nulle part, pas Ă©vidente, mais cela a bien payĂ©. J'ai adoptĂ© l'inline agressive et cela m'a amenĂ© dans des endroits oĂč les performances Java et C fonctionnent cĂŽte Ă cĂŽte. Ils sont trĂšs proches â je peux Ă©crire du code Java qui est nettement plus rapide que le code C et des choses comme ça, mais en moyenne, dans lâensemble, ils sont Ă peu prĂšs comparables. Je pense qu'une partie de ce mĂ©rite rĂ©side dans l'allocateur de registre, qui me permet d'intĂ©grer le plus bĂȘtement possible. J'intĂšgre simplement tout ce que je vois. La question ici est de savoir si lâallocateur fonctionne bien, si le rĂ©sultat est un code qui fonctionne intelligemment. C'Ă©tait un grand dĂ©fi : comprendre tout cela et faire en sorte que cela fonctionne.
Un peu sur l'allocation des registres et les multicĆurs
Vladimir: Les problĂšmes tels que l'allocation des registres semblent ĂȘtre une sorte de sujet Ă©ternel et sans fin. Je me demande sâil y a dĂ©jĂ eu une idĂ©e qui semblait prometteuse et qui a ensuite Ă©chouĂ© dans la pratique ?
falaise: Certainement! L'allocation de registre est un domaine dans lequel vous essayez de trouver des heuristiques pour rĂ©soudre un problĂšme NP-complet. Et on ne peut jamais parvenir Ă une solution parfaite, nâest-ce pas ? C'est tout simplement impossible. Regardez, la compilation Ahead of Time - elle fonctionne Ă©galement mal. La conversation ici porte sur quelques cas moyens. Ă propos des performances typiques, afin que vous puissiez mesurer quelque chose que vous pensez ĂȘtre une bonne performance typique - aprĂšs tout, vous travaillez pour l'amĂ©liorer ! L'allocation de registres est un sujet entiĂšrement axĂ© sur les performances. Une fois que vous avez le premier prototype, il fonctionne et peint ce qui est nĂ©cessaire, le travail de performance commence. Il faut apprendre Ă bien mesurer. Pourquoi c'est important? Si vous disposez de donnĂ©es claires, vous pouvez regarder diffĂ©rents domaines et voir : oui, ça a aidĂ© ici, mais câest lĂ que tout sâest cassĂ© ! De bonnes idĂ©es surgissent, vous ajoutez de nouvelles heuristiques et tout dâun coup, tout commence Ă fonctionner un peu mieux en moyenne. Ou alors, il ne dĂ©marre pas. J'ai eu de nombreux cas oĂč nous nous battions pour obtenir les cinq pour cent de performance qui diffĂ©renciaient notre dĂ©veloppement de l'allocateur prĂ©cĂ©dent. Et Ă chaque fois, cela ressemble Ă ceci : quelque part vous gagnez, quelque part vous perdez. Si vous disposez de bons outils dâanalyse des performances, vous pouvez trouver les idĂ©es perdantes et comprendre pourquoi elles Ă©chouent. Cela vaut peut-ĂȘtre la peine de tout laisser tel quel, ou peut-ĂȘtre d'adopter une approche plus sĂ©rieuse pour peaufiner le tout, ou de sortir et de rĂ©parer autre chose. C'est tout un tas de choses ! J'ai fait ce hack sympa, mais j'ai aussi besoin de celui-ci, et de celui-ci, et de celui-ci - et leur combinaison totale donne quelques amĂ©liorations. Et les solitaires peuvent Ă©chouer. C'est la nature du travail de performance sur les problĂšmes NP-complets.
Vladimir: On a le sentiment que des choses comme la peinture dans les répartiteurs sont un problÚme qui a déjà été résolu. Eh bien, c'est décidé pour vous, à en juger par ce que vous dites, alors est-ce que ça vaut le coup...
falaise: Ce n'est pas rĂ©solu en tant que tel. C'est vous qui devez le transformer en « rĂ©solu ». Il existe des problĂšmes difficiles et il faut les rĂ©soudre. Une fois cela fait, il est temps de travailler sur la productivitĂ©. Vous devez aborder ce travail en consĂ©quence - effectuer des benchmarks, collecter des mĂ©triques, expliquer les situations dans lesquelles, lorsque vous ĂȘtes revenu Ă une version prĂ©cĂ©dente, votre ancien hack a recommencĂ© Ă fonctionner (ou vice versa, s'est arrĂȘtĂ©). Et n'abandonnez pas tant que vous n'avez pas rĂ©ussi quelque chose. Comme je l'ai dĂ©jĂ dit, s'il y a des idĂ©es sympas qui n'ont pas fonctionnĂ©, mais dans le domaine de la rĂ©partition des registres d'idĂ©es, c'est Ă peu prĂšs infini. Vous pouvez par exemple lire des publications scientifiques. Bien que maintenant, ce domaine ait commencĂ© Ă Ă©voluer beaucoup plus lentement et soit devenu plus clair que dans sa jeunesse. Cependant, dâinnombrables personnes travaillent dans ce domaine et toutes leurs idĂ©es valent la peine dâĂȘtre essayĂ©es, elles attendent toutes dans les coulisses. Et vous ne pouvez pas dire Ă quel point ils sont bons Ă moins de les essayer. Dans quelle mesure s'intĂšgrent-ils bien Ă tout le reste de votre allocateur, car un allocateur fait beaucoup de choses et certaines idĂ©es ne fonctionneront pas dans votre allocateur spĂ©cifique, mais dans un autre allocateur, elles fonctionneront facilement. Le principal moyen de gagner pour l'allocateur est de retirer les Ă©lĂ©ments lents en dehors du chemin principal et de les forcer Ă se diviser le long des limites des chemins lents. Donc, si vous voulez exĂ©cuter un GC, emprunter le chemin lent, dĂ©soptimiser, lever une exception, tout ça - vous savez que ces choses sont relativement rares. Et ils sont vraiment rares, j'ai vĂ©rifiĂ©. Vous effectuez un travail supplĂ©mentaire et cela supprime de nombreuses restrictions sur ces chemins lents, mais cela n'a pas vraiment d'importance car ils sont lents et rarement parcourus. Par exemple, un pointeur nul â cela nâarrive jamais, nâest-ce pas ? Vous devez avoir plusieurs chemins pour diffĂ©rentes choses, mais ils ne doivent pas interfĂ©rer avec le chemin principal.
Vladimir: Que pensez-vous du multicĆur, quand il y a des milliers de cĆurs Ă la fois ? Est-ce une chose utile ?
falaise: Le succĂšs du GPU montre qu'il est plutĂŽt utile !
Vladimir: Ils sont assez spĂ©cialisĂ©s. Quâen est-il des processeurs Ă usage gĂ©nĂ©ral ?
falaise: Eh bien, c'Ă©tait le modĂšle Ă©conomique d'Azul. La rĂ©ponse est revenue Ă une Ă©poque oĂč les gens aimaient vraiment les performances prĂ©visibles. Il Ă©tait alors difficile dâĂ©crire du code parallĂšle. Le modĂšle de codage H2O est hautement Ă©volutif, mais il ne sâagit pas dâun modĂšle Ă usage gĂ©nĂ©ral. Peut-ĂȘtre un peu plus gĂ©nĂ©ral que lors de l'utilisation d'un GPU. Parlons-nous de la complexitĂ© de dĂ©velopper une telle chose ou de la complexitĂ© de son utilisation ? Par exemple, Azul m'a appris une leçon intĂ©ressante, mais pas Ă©vidente : les petites caches sont normales.
Le plus grand défi de la vie
Vladimir: Quâen est-il des dĂ©fis non techniques ?
falaise: Le plus grand dĂ©fi n'Ă©tait pas d'ĂȘtre... gentil et gentil avec les gens. Et du coup, je me suis retrouvĂ© constamment dans des situations extrĂȘmement conflictuelles. Ceux oĂč je savais que les choses allaient mal, mais je ne savais pas comment rĂ©soudre ces problĂšmes et je ne pouvais pas les gĂ©rer. De nombreux problĂšmes Ă long terme, qui ont durĂ© des dĂ©cennies, sont ainsi survenus. Le fait que Java dispose de compilateurs C1 et C2 en est une consĂ©quence directe. Le fait quâil nây ait pas eu de compilation multi-niveaux en Java pendant dix annĂ©es consĂ©cutives est Ă©galement une consĂ©quence directe. Il est Ă©vident que nous avions besoin dâun tel systĂšme, mais la raison pour laquelle il nâexistait pas nâest pas Ă©vidente. J'ai eu des problĂšmes avec un ingĂ©nieur... ou un groupe d'ingĂ©nieurs. Il Ă©tait une fois, quand j'ai commencĂ© Ă travailler chez Sun, j'Ă©tais... D'accord, pas seulement Ă ce moment-lĂ , j'ai gĂ©nĂ©ralement toujours ma propre opinion sur tout. Et je pensais qu'il Ă©tait vrai que tu pouvais simplement prendre ta vĂ©ritĂ© et la dire de front. Dâautant plus que jâavais incroyablement raison la plupart du temps. Et si vous nâaimez pas cette approche⊠surtout si vous vous trompez manifestement et faites des bĂȘtises⊠En gĂ©nĂ©ral, peu de gens pourraient tolĂ©rer cette forme de communication. MĂȘme si certains le pourraient, comme moi. J'ai construit toute ma vie sur des principes mĂ©ritocratiques. Si vous me montrez quelque chose qui ne va pas, je me retournerai immĂ©diatement et je dirai : vous avez dit des bĂȘtises. En mĂȘme temps, bien sĂ»r, je m'excuse et tout ça, je noterai les mĂ©rites, le cas Ă©chĂ©ant, et prendrai d'autres mesures correctes. Dâun autre cĂŽtĂ©, jâai incroyablement raison sur un pourcentage incroyablement Ă©levĂ© du temps total. Et cela ne fonctionne pas trĂšs bien dans les relations avec les gens. Je nâessaie pas dâĂȘtre gentil, mais je pose la question sans dĂ©tour. "Cela ne marchera jamais, car un, deux et trois." Et ils disaient : « Oh ! Il y avait dâautres consĂ©quences quâil valait probablement mieux ignorer : par exemple, celles qui ont conduit au divorce dâavec ma femme et Ă dix ans de dĂ©pression par la suite.
Le dĂ©fi est une lutte avec les gens, avec leur perception de ce que vous pouvez ou ne pouvez pas faire, de ce qui est important et de ce qui ne l'est pas. Il y avait de nombreux dĂ©fis concernant le style de codage. J'Ă©cris encore beaucoup de code, et Ă cette Ă©poque, je devais mĂȘme ralentir parce que je faisais trop de tĂąches parallĂšles et que je les faisais mal, au lieu de me concentrer sur une seule. Avec le recul, j'ai Ă©crit la moitiĂ© du code de la commande Java JIT, la commande C2. Le codeur le plus rapide suivant Ă©crivait moitiĂ© moins lentement, le suivant moitiĂ© moins lent, et ce fut un dĂ©clin exponentiel. La septiĂšme personne dans cette rangĂ©e Ă©tait trĂšs, trĂšs lente â cela arrive toujours ! J'ai touchĂ© beaucoup de code. J'ai regardĂ© qui a Ă©crit quoi, sans exception, j'ai regardĂ© leur code, j'ai examinĂ© chacun d'eux et j'ai quand mĂȘme continuĂ© Ă Ă©crire plus moi-mĂȘme que n'importe lequel d'entre eux. Cette approche ne fonctionne pas trĂšs bien avec les gens. Certaines personnes n'aiment pas ça. Et quand ils nây parviennent pas, toutes sortes de plaintes commencent. Par exemple, on m'a dit un jour d'arrĂȘter de coder parce que j'Ă©crivais trop de code et que cela mettait l'Ă©quipe en danger, et tout cela m'a semblĂ© ĂȘtre une blague : mec, si le reste de l'Ă©quipe disparaĂźt et que je continue d'Ă©crire du code, tu Je ne perdrai que la moitiĂ© des Ă©quipes. D'un autre cĂŽtĂ©, si je continue Ă Ă©crire du code et que vous perdez la moitiĂ© de l'Ă©quipe, cela ressemble Ă une trĂšs mauvaise gestion. Je nây ai jamais vraiment pensĂ©, je nâen ai jamais parlĂ©, mais câĂ©tait encore quelque part dans ma tĂȘte. La pensĂ©e tournait au fond de mon esprit : « Vous vous moquez de moi ? Donc le plus gros problĂšme, câĂ©tait moi et mes relations avec les gens. Maintenant, je me comprends beaucoup mieux, j'ai longtemps Ă©tĂ© chef d'Ă©quipe de programmeurs, et maintenant je dis directement aux gens : vous savez, je suis qui je suis, et vous devrez vous occuper de moi - est-ce que ça va si je me tiens debout ici? Et quand ils ont commencĂ© Ă sâen occuper, tout a fonctionnĂ©. En fait, je ne suis ni mauvais ni bon, je nâai pas de mauvaises intentions ni dâaspirations Ă©goĂŻstes, câest juste mon essence et je dois vivre avec dâune maniĂšre ou dâune autre.
Andrew: Tout récemment, tout le monde a commencé à parler de conscience de soi pour les introvertis et de compétences générales en général. Que pouvez-vous dire à ce sujet ?
falaise: Oui, c'est la perspicacitĂ© et la leçon que j'ai tirĂ©es de mon divorce avec ma femme. Ce que j'ai appris du divorce, c'est de me comprendre. Câest ainsi que jâai commencĂ© Ă comprendre les autres. Comprenez comment fonctionne cette interaction. Cela a conduit Ă des dĂ©couvertes les unes aprĂšs les autres. Il y avait une prise de conscience de qui je suis et de ce que je reprĂ©sente. Qu'est-ce que je fais : soit je suis prĂ©occupĂ© par la tĂąche, soit j'Ă©vite les conflits, soit autre chose - et ce niveau de conscience de soi m'aide vraiment Ă garder le contrĂŽle. AprĂšs cela, tout devient beaucoup plus facile. Une chose que j'ai dĂ©couverte non seulement chez moi, mais aussi chez d'autres programmeurs, c'est l'incapacitĂ© de verbaliser ses pensĂ©es lorsque l'on est dans un Ă©tat de stress Ă©motionnel. Par exemple, vous ĂȘtes assis lĂ Ă coder, dans un Ă©tat de flux, puis ils courent vers vous et commencent Ă crier de maniĂšre hystĂ©rique que quelque chose est cassĂ© et que maintenant des mesures extrĂȘmes seront prises contre vous. Et vous ne pouvez pas dire un mot parce que vous ĂȘtes dans un Ă©tat de stress Ă©motionnel. Les connaissances acquises permettent de se prĂ©parer Ă ce moment, d'y survivre et de passer Ă un plan de retraite, aprĂšs quoi vous pourrez faire quelque chose. Alors oui, lorsque vous commencez Ă comprendre comment tout cela fonctionne, câest un Ă©norme Ă©vĂ©nement qui change la vie.
Je n'ai moi-mĂȘme pas trouvĂ© les mots justes, mais je me suis souvenu de la sĂ©quence d'actions. Le fait est que cette rĂ©action est autant physique que verbale et que vous avez besoin dâespace. Un tel espace, au sens zen. C'est exactement ce qu'il faut expliquer, puis s'Ă©carter immĂ©diatement - s'Ă©loigner purement physiquement. Lorsque je reste verbalement silencieux, je peux gĂ©rer la situation Ă©motionnellement. Lorsque l'adrĂ©naline atteint votre cerveau, vous fait passer en mode combat ou fuite, vous ne pouvez plus rien dire, non - maintenant vous ĂȘtes un idiot, un ingĂ©nieur fouet, incapable de rĂ©agir correctement ou mĂȘme d'arrĂȘter l'attaque, et l'attaquant est libre. pour attaquer encore et encore. Il faut dâabord redevenir soi-mĂȘme, reprendre le contrĂŽle, sortir du mode « combat ou fuite ».
Et pour cela nous avons besoin dâespace verbal. Juste de l'espace libre. Si vous dites n'importe quoi, alors vous pouvez dire exactement cela, puis aller vraiment trouver un « espace » pour vous-mĂȘme : faire une promenade dans le parc, vous enfermer sous la douche - cela n'a pas d'importance. Lâessentiel est de se dĂ©connecter temporairement de cette situation. DĂšs que vous vous Ă©teignez pendant au moins quelques secondes, le contrĂŽle revient, vous commencez Ă rĂ©flĂ©chir sobrement. "D'accord, je ne suis pas une sorte d'idiot, je ne fais pas de bĂȘtises, je suis une personne plutĂŽt utile." Une fois que vous avez rĂ©ussi Ă vous convaincre, il est temps de passer Ă lâĂ©tape suivante : comprendre ce qui sâest passĂ©. Vous avez Ă©tĂ© attaquĂ©, lâattaque est venue dâoĂč vous ne vous y attendiez pas, câĂ©tait une embuscade malhonnĂȘte et ignoble. C'est mauvais. LâĂ©tape suivante consiste Ă comprendre pourquoi lâattaquant en avait besoin. Vraiment pourquoi? Peut-ĂȘtre parce qu'il est lui-mĂȘme furieux ? Pourquoi est-il fou ? Par exemple, parce qu'il s'est trompĂ© et ne peut pas accepter ses responsabilitĂ©s ? Câest la maniĂšre de gĂ©rer soigneusement lâensemble de la situation. Mais cela nĂ©cessite une marge de manĆuvre, un espace verbal. La toute premiĂšre Ă©tape consiste Ă rompre le contact verbal. Ăvitez les discussions avec des mots. Annulez-le, partez le plus vite possible. S'il s'agit d'une conversation tĂ©lĂ©phonique, raccrochez - c'est une compĂ©tence que j'ai acquise en communiquant avec mon ex-femme. Si la conversation ne mĂšne Ă rien de bon, dites simplement « au revoir » et raccrochez. De lâautre cĂŽtĂ© du tĂ©lĂ©phone : « bla bla bla », vous rĂ©pondez : « ouais, au revoir ! et raccroche. Vous venez de mettre fin Ă la conversation. Cinq minutes plus tard, lorsque la capacitĂ© de penser raisonnablement vous revient, que vous vous ĂȘtes un peu refroidi, il devient possible de penser Ă tout, Ă ce qui s'est passĂ© et Ă ce qui se passera ensuite. Et commencez Ă formuler une rĂ©ponse rĂ©flĂ©chie, plutĂŽt que de simplement rĂ©agir par Ă©motion. Pour moi, la percĂ©e dans la conscience de soi Ă©tait prĂ©cisĂ©ment le fait qu'en cas de stress Ă©motionnel, je ne pouvais pas parler. Sortir de cet Ă©tat, rĂ©flĂ©chir et planifier comment rĂ©agir et compenser les problĂšmes - ce sont les bonnes Ă©tapes dans le cas oĂč vous ne pouvez pas parler. Le moyen le plus simple est de fuir la situation dans laquelle le stress Ă©motionnel se manifeste et de simplement cesser de participer Ă ce stress. AprĂšs cela, vous devenez capable de penser, quand vous pouvez penser, vous devenez capable de parler, et ainsi de suite.
D'ailleurs, au tribunal, l'avocat adverse essaie de vous faire cela - on comprend maintenant pourquoi. Parce quâil a la capacitĂ© de vous rĂ©primer Ă un point tel que vous ne pouvez mĂȘme pas prononcer votre nom, par exemple. Dans un sens trĂšs rĂ©el, vous ne pourrez pas parler. Si cela vous arrive, et si vous savez que vous vous retrouverez dans un endroit oĂč les batailles verbales font rage, dans un lieu comme un tribunal, alors vous pouvez venir avec votre avocat. L'avocat prendra votre dĂ©fense et mettra fin Ă l'attaque verbale, et le fera de maniĂšre tout Ă fait lĂ©gale, et l'espace Zen perdu vous reviendra. Par exemple, jâai dĂ» appeler ma famille Ă plusieurs reprises, le juge sâest montrĂ© plutĂŽt amical Ă ce sujet, mais lâavocat de la partie adverse mâa criĂ© dessus, je nâai mĂȘme pas rĂ©ussi Ă faire passer un mot. Dans ces cas-lĂ , le recours Ă un mĂ©diateur me convient le mieux. Le mĂ©diateur arrĂȘte toute cette pression qui s'abat sur vous en flux continu, vous retrouvez l'espace zen nĂ©cessaire, et avec lui la capacitĂ© de parler revient. Il s'agit de tout un domaine de connaissances dans lequel il y a beaucoup Ă Ă©tudier, beaucoup Ă dĂ©couvrir en soi, et tout cela se transforme en dĂ©cisions stratĂ©giques de haut niveau qui sont diffĂ©rentes selon les personnes. Certaines personnes nâont pas les problĂšmes dĂ©crits ci-dessus ; gĂ©nĂ©ralement, les vendeurs professionnels nâen ont pas. Tous ces gens qui vivent des mots â chanteurs, poĂštes, chefs religieux et hommes politiques cĂ©lĂšbres â ont toujours quelque chose Ă dire. Ils n'ont pas de tels problĂšmes, mais moi oui.
Andrew: C'était... inattendu. Super, nous avons déjà beaucoup parlé et il est temps de terminer cette interview. Nous nous retrouverons certainement à la conférence et pourrons poursuivre ce dialogue. Rendez-vous à Hydra!
Vous pouvez poursuivre votre conversation avec Cliff lors de la confĂ©rence Hydra 2019, qui se tiendra les 11 et 12 juillet 2019 Ă Saint-PĂ©tersbourg. Il viendra avec un rapport . Les billets peuvent ĂȘtre achetĂ©s .
Source: habr.com
