Gran entrevista con Cliff Click, o pai da compilación JIT en Java

Gran entrevista con Cliff Click, o pai da compilación JIT en JavaCliff Click — CTO de Cratus (sensores de IoT para a mellora de procesos), fundador e cofundador de varias startups (incluíndo Rocket Realtime School, Neurensic e H2O.ai) con varias saídas exitosas. Cliff escribiu o seu primeiro compilador aos 15 anos (Pascal para o TRS Z-80). É máis coñecido polo seu traballo en C2 en Java (o Sea of ​​​​Nodes IR). Este compilador mostroulle ao mundo que JIT podía producir código de alta calidade, que foi un dos factores da aparición de Java como unha das principais plataformas de software modernas. Entón Cliff axudou a Azul Systems a construír un mainframe de 864 núcleos con software Java puro que admitía pausas de GC nun montón de 500 gigabytes en 10 milisegundos. En xeral, Cliff conseguiu traballar en todos os aspectos da JVM.

 
Este habrapost é unha gran entrevista con Cliff. Falaremos dos seguintes temas:

  • Transición a optimizacións de baixo nivel
  • Como facer unha gran refactorización
  • Modelo de custos
  • Formación en optimización de baixo nivel
  • Exemplos prácticos de mellora do rendemento
  • Por que crear a súa propia linguaxe de programación
  • Carreira de Enxeñeiro de Performance
  • Retos técnicos
  • Un pouco sobre a asignación de rexistros e multinúcleos
  • O maior reto da vida

As entrevistas son realizadas por:

  • Andrei Satarin de Amazon Web Services. Na súa carreira, conseguiu traballar en proxectos completamente diferentes: probou a base de datos distribuída NewSQL en Yandex, un sistema de detección na nube en Kaspersky Lab, un xogo multixogador en Mail.ru e un servizo para calcular os prezos das divisas en Deutsche Bank. Interesado en probar sistemas distribuídos e backend a gran escala.
  • Vladimir Sitnikov de Netcracker. Dez anos de traballo no rendemento e escalabilidade do sistema operativo NetCracker, software utilizado polos operadores de telecomunicacións para automatizar os procesos de xestión de equipos de rede e rede. Interesado en problemas de rendemento de Java e Oracle Database. Autor de máis dunha ducia de melloras de rendemento no controlador JDBC oficial de PostgreSQL.

Transición a optimizacións de baixo nivel

Andrew: Es un gran nome no mundo da compilación JIT, Java e do traballo de rendemento en xeral, non? 

Acantilado: É así!

Andrew: Imos comezar con algunhas preguntas xerais sobre o traballo de performance. Que pensas sobre a elección entre optimizacións de alto e baixo nivel como traballar a nivel de CPU?

Acantilado: Si, aquí todo é sinxelo. O código máis rápido é o que nunca se executa. Polo tanto, sempre cómpre comezar desde un alto nivel, traballar en algoritmos. Unha notación O mellor superará a unha notación O peor a menos que interveñan unhas constantes suficientemente grandes. As cousas de baixo nivel van as últimas. Normalmente, se optimizaches o resto da túa pila o suficientemente ben e aínda quedan algunhas cousas interesantes, é un nivel baixo. Pero como comezar desde un alto nivel? Como sabes que se fixo o suficiente traballo de alto nivel? Pois... de ningún xeito. Non hai receitas preparadas. Cómpre comprender o problema, decidir o que vas facer (para non dar pasos innecesarios no futuro) e despois podes descubrir o perfilador, que pode dicir algo útil. Nalgún momento, vostede mesmo dáse conta de que se desfixo de cousas innecesarias e é hora de facer algúns axustes de baixo nivel. Este é, sen dúbida, un tipo de arte especial. Hai moita xente facendo cousas innecesarias, pero movéndose tan rápido que non teñen tempo para preocuparse pola produtividade. Pero isto é ata que a pregunta xorde sen rodeos. Normalmente o 99% das veces a ninguén lle importa o que fago, ata o momento en que aparece unha cousa importante no camiño crítico que a ninguén lle importa. E aquí todo o mundo comeza a molestarche sobre "por que non funcionou perfectamente desde o principio". En xeral, sempre hai algo que mellorar no rendemento. Pero o 99% das veces non tes pistas! Só estás intentando facer que algo funcione e no proceso descobres o que é importante. Nunca podes saber de antemán que esta peza ten que ser perfecta, polo que, de feito, tes que ser perfecta en todo. Pero isto é imposible e non o fas. Sempre hai moitas cousas que arranxar, e iso é completamente normal.

Como facer unha gran refactorización

Andrew: Como traballas nunha actuación? Este é un problema transversal. Por exemplo, algunha vez tivo que traballar en problemas que xorden da intersección de moitas funcionalidades existentes?

Acantilado: Intento evitalo. Se sei que o rendemento será un problema, penso niso antes de comezar a codificar, especialmente coas estruturas de datos. Pero moitas veces descobres todo isto moi tarde. E entón tes que ir a medidas extremas e facer o que eu chamo "escribir e conquistar": tes que coller unha peza o suficientemente grande. Parte do código aínda terá que reescribirse por problemas de rendemento ou por outra cousa. Sexa cal sexa o motivo para reescribir o código, case sempre é mellor reescribir unha peza máis grande que unha peza máis pequena. Neste momento, todos comezan a tremer de medo: "Oh meu Deus, non podes tocar tanto código!" Pero de feito, este enfoque case sempre funciona moito mellor. Debe asumir inmediatamente un gran problema, debuxar un círculo grande ao seu redor e dicir: Vou reescribir todo o que está dentro do círculo. O bordo é moito máis pequeno que o contido que hai que substituír. E se tal delimitación de límites che permite facer o traballo por dentro perfectamente, as túas mans están libres, fai o que queiras. Unha vez que entendas o problema, o proceso de reescritura é moito máis sinxelo, así que dá un bocado grande!
Ao mesmo tempo, cando fas unha reescritura grande e te das conta de que o rendemento vai ser un problema, podes comezar a preocuparte inmediatamente por iso. Isto xeralmente convértese en cousas simples como "non copie datos, xestione os datos o máis sinxelo posible, faille pequeno". Nas reescrituras grandes, hai formas estándar de mellorar o rendemento. E case sempre xiran arredor dos datos.

Modelo de custos

Andrew: Nun dos podcasts falaches dos modelos de custos no contexto da produtividade. Podes explicar o que querías dicir con isto?

Acantilado: Certamente. Nacín nunha época na que o rendemento do procesador era moi importante. E esta época volve de novo: o destino non está exento de ironía. Comecei a vivir nos tempos das máquinas de oito bits; o meu primeiro ordenador funcionaba con 256 bytes. Exactamente bytes. Todo era moi pequeno. Había que contar as instrucións e, a medida que comezamos a subir na pila de linguaxes de programación, as linguaxes foron cada vez máis. Houbo Assembler, despois Basic, despois C e C encargouse de moitos detalles, como a asignación de rexistros e a selección de instrucións. Pero todo estaba bastante claro alí, e se fixera un punteiro a unha instancia dunha variable, entón obtería carga e coñécese o custo desta instrución. O hardware produce un certo número de ciclos da máquina, polo que a velocidade de execución de diferentes cousas pódese calcular simplemente sumando todas as instrucións que vai executar. Cada comparación/proba/filial/chamada/carga/almacenamento podería engadirse e dicir: ese é o tempo de execución para ti. Ao traballar para mellorar o rendemento, definitivamente prestará atención a que números corresponden a pequenos ciclos quentes. 
Pero en canto cambias a Java, Python e cousas similares, afastaste moi rapidamente do hardware de baixo nivel. Cal é o custo de chamar a un getter en Java? Se JIT no HotSpot é correcto aliñados, cargarase, pero se non o fixo, será unha chamada de función. Dado que a chamada está nun bucle quente, anulará todas as outras optimizacións dese bucle. Polo tanto, o custo real será moito maior. E inmediatamente perde a capacidade de mirar un anaco de código e entender que debemos executalo en termos de velocidade de reloxo do procesador, memoria e caché utilizados. Todo isto só se fai interesante se realmente entras na actuación.
Agora atopámonos nunha situación na que as velocidades dos procesadores case non aumentaron durante unha década. Volven os vellos tempos! Xa non podes contar cun bo rendemento dun só fío. Pero se de súpeto te metes na informática paralela, é incriblemente difícil, todos miran para ti como James Bond. Aquí adoitan producirse aceleracións dez veces nos lugares onde alguén estropeou algo. A concorrencia require moito traballo. Para conseguir esa aceleración de XNUMX veces, cómpre comprender o modelo de custo. Que e canto custa? E para iso, cómpre comprender como a lingua encaixa no hardware subxacente.
Martin Thompson escolleu unha palabra estupenda para o seu blog Simpatía mecánica! Debe entender o que vai facer o hardware, como o fará exactamente e por que fai o que fai en primeiro lugar. Usando isto, é bastante sinxelo comezar a contar instrucións e descubrir cara a onde vai o tempo de execución. Se non tes a formación adecuada, só buscas un gato negro nun cuarto escuro. Vexo xente optimizando o rendemento todo o tempo que non ten nin idea de que diaños están facendo. Sofren moito e non avanzan moito. E cando tomo o mesmo anaco de código, introduzo un par de pequenos trucos e consigo unha aceleración de cinco ou dez veces, son como: ben, iso non é xusto, xa sabiamos que eras mellor. Incrible. De que estou a falar... o modelo de custo trata sobre o tipo de código que escribes e a rapidez con que se executa de media no panorama xeral.

Andrew: E como podes manter ese volume na túa cabeza? Isto conséguese con máis experiencia, ou? De onde vén esa experiencia?

Acantilado: Ben, non teño a miña experiencia do xeito máis sinxelo. Programei en Asemblea nos tempos en que podías entender todas as instrucións. Parece estúpido, pero desde entón o conxunto de instrucións Z80 sempre permaneceu na miña cabeza, na miña memoria. Non lembro os nomes das persoas nun minuto despois de falar, pero lembro o código escrito hai 40 anos. É gracioso, parece unha síndrome"científico idiota».

Formación en optimización de baixo nivel

Andrew: Hai unha forma máis sinxela de entrar?

Acantilado: Si e non. O hardware que todos usamos non cambiou tanto ao longo do tempo. Todo o mundo usa x86, a excepción dos teléfonos intelixentes Arm. Se non estás facendo algún tipo de incorporación hardcore, estás facendo o mesmo. Vale, segue adiante. As instrucións tampouco cambiaron durante séculos. Hai que ir escribir algo en Asemblea. Non moito, pero o suficiente para comezar a entender. Estás sorrindo, pero falo totalmente en serio. Cómpre comprender a correspondencia entre linguaxe e hardware. Despois diso, cómpre ir escribir un pouco e facer un pequeno compilador de xoguetes para unha pequena linguaxe de xoguetes. Parecido a un xoguete significa que debe facerse nun período de tempo razoable. Pode ser moi sinxelo, pero debe xerar instrucións. O feito de xerar unha instrución axudarache a comprender o modelo de custo para a ponte entre o código de alto nivel que todos escriben e o código de máquina que se executa no hardware. Esta correspondencia gravarase no cerebro no momento en que se escriba o compilador. Incluso o compilador máis sinxelo. Despois diso, podes comezar a mirar Java e o feito de que o seu abismo semántico é moito máis profundo, e é moito máis difícil construír pontes sobre el. En Java, é moito máis difícil entender se a nosa ponte resultou boa ou mala, que fará que se desmorone e que non. Pero necesitas algún tipo de punto de partida no que mires o código e entendas: "si, este buscador debería estar en liña cada vez". E entón resulta que ás veces isto ocorre, excepto na situación na que o método se fai demasiado grande e o JIT comeza a integrar todo. O rendemento destes lugares pódese prever ao instante. Normalmente os getters funcionan ben, pero despois miras grandes bucles quentes e dáse conta de que hai algunhas chamadas de función flotando por alí que non saben o que están a facer. Este é o problema do uso xeneralizado dos getters, a razón pola que non están inlineados é que non está claro se son getter. Se tes unha base de código súper pequena, simplemente podes lembrala e dicir: este é un getter e este é un setter. Nunha gran base de código, cada función vive a súa propia historia, que, en xeral, non é coñecida por ninguén. O perfilador di que perdemos o 24 % do tempo nalgún bucle e para entender o que está a facer este bucle, necesitamos ver cada función dentro. É imposible entender isto sen estudar a función, e isto ralentiza seriamente o proceso de comprensión. Por iso non uso getters nin setters, cheguei a un novo nivel!
Onde conseguir o modelo de custo? Pois podes ler algo, claro... Pero creo que o mellor é actuar. Facer un pequeno compilador será a mellor forma de entender o modelo de custo e encaixalo na túa propia cabeza. Un pequeno compilador que sería axeitado para programar un microondas é unha tarefa para un principiante. Ben, quero dicir, se xa tes habilidades de programación, entón iso debería ser suficiente. Todas estas cousas como analizar unha cadea que ten como algún tipo de expresión alxébrica, extraer instrucións para operacións matemáticas a partir de aí na orde correcta, tomar os valores correctos dos rexistros, todo isto faise á vez. E mentres o fas, quedará impreso no teu cerebro. Creo que todo o mundo sabe o que fai un compilador. E isto dará unha comprensión do modelo de custo.

Exemplos prácticos de mellora do rendemento

Andrew: A que máis debes prestar atención cando traballas na produtividade?

Acantilado: Estruturas de datos. Por certo, si, hai tempo que non imparto estas clases... Escola de foguetes. Foi divertido, pero requiriu moito esforzo, e tamén teño unha vida! OK. Entón, nunha das grandes e interesantes clases, "Onde vai o teu rendemento", puxen un exemplo aos alumnos: líanse dous gigabytes e medio de datos fintech dun ficheiro CSV e despois tiñan que calcular o número de produtos vendidos. . Datos regulares do mercado de garrapatas. Os paquetes UDP convertéronse a formato de texto desde os anos 70. Chicago Mercantile Exchange - todo tipo de cousas como manteiga, millo, soia, cousas así. Foi necesario contar estes produtos, o número de transaccións, o volume medio de movemento de fondos e mercadorías, etc. Son matemáticas de negociación bastante sinxelas: atopa o código do produto (é dicir, 1-2 caracteres na táboa hash), obtén a cantidade, engádea a un dos conxuntos de comercio, engade volume, engade valor e un par de cousas máis. Matemáticas moi sinxelas. A implementación do xoguete foi moi sinxela: todo está nun ficheiro, leo o ficheiro e paso por el, dividindo rexistros individuais en cadeas Java, buscando neles as cousas necesarias e sumándoas segundo as matemáticas descritas anteriormente. E funciona a baixa velocidade.

Con este enfoque, é obvio o que está a suceder, e a computación paralela non axudará, non? Resulta que un aumento de cinco veces no rendemento pódese conseguir simplemente escollendo as estruturas de datos correctas. E isto sorprende ata os programadores experimentados! No meu caso particular, o truco foi que non deberías facer asignacións de memoria nun bucle quente. Ben, esta non é toda a verdade, pero en xeral, non debes destacar "unha vez en X" cando X é o suficientemente grande. Cando X é de dous gigabytes e medio, non debes asignar nada "unha vez por letra", nin "unha vez por liña", nin "unha vez por campo", nada así. Aquí é onde se gasta o tempo. Como funciona isto? Imaxina que fago unha chamada String.split() ou BufferedReader.readLine(). Readline fai unha cadea a partir dun conxunto de bytes que chegaron pola rede, unha vez por cada liña, por cada un dos centos de millóns de liñas. Collo esta liña, analízoa e tíroa. Por que o tiro? Ben, xa o procesei, iso é todo. Entón, por cada byte lido destes 2.7G escribiranse dous caracteres na liña, é dicir, xa 5.4G, e non os necesito para nada máis, polo que bótanse. Se observas o ancho de banda da memoria, cargamos 2.7 G que atravesan a memoria e o bus de memoria do procesador, e despois envíase o dobre á liña que está na memoria, e todo isto desfrázase cando se crea cada liña nova. Pero teño que lelo, o hardware leo, aínda que máis tarde todo se desgaste. E teño que anotalo porque creei unha liña e as cachés están cheas; a caché non pode acomodar 2.7G. Entón, por cada byte que lin, leo dous bytes máis e escribo dous bytes máis, e ao final teñen unha proporción de 4:1; nesta proporción estamos a perder ancho de banda da memoria. E despois resulta que se o fago String.split() – Esta non é a última vez que fago isto, pode haber outros 6-7 campos dentro. Entón, o código clásico de ler CSV e despois analizar as cadeas desperdicia uns 14:1 do ancho de banda de memoria que realmente queres. Se tiras estas seleccións, podes conseguir unha aceleración de cinco veces.

E non é tan difícil. Se miras o código desde o ángulo correcto, todo se fai bastante sinxelo unha vez que te das conta do problema. Non debes deixar de asignar memoria por completo: o único problema é que asignas algo e morre inmediatamente, e ao longo do camiño queima un recurso importante, que neste caso é o ancho de banda da memoria. E todo isto resulta nunha baixada da produtividade. En x86 normalmente necesitas queimar activamente os ciclos do procesador, pero aquí gravaches toda a memoria moito antes. A solución é reducir a cantidade de descarga. 
A outra parte do problema é que se executas o perfilador cando se esgota a franxa de memoria, xusto cando ocorre, normalmente estás esperando a que volva a caché porque está chea de lixo que acabas de producir, todas esas liñas. Polo tanto, cada operación de carga ou almacenamento tórnase lenta, porque provocan erros na caché: toda a caché volveuse lenta, esperando que o lixo saia. Polo tanto, o perfilador só mostrará un ruído aleatorio cálido manchado ao longo de todo o ciclo; non haberá instrucións quentes ou lugar separados no código. Só ruído. E se miras os ciclos de GC, todos son da Xeración Xove e super rápidos: microsegundos ou milisegundos como máximo. Despois de todo, toda esta memoria morre ao instante. Asignas miles de millóns de gigabytes, e el córtaos, e córtaos e córtaos de novo. Todo isto ocorre moi rápido. Resulta que hai ciclos de GC baratos, ruído cálido ao longo de todo o ciclo, pero queremos conseguir unha aceleración 5 veces. Neste momento, algo debería pecharse na cabeza e soar: "¿Por que isto?!" O desbordamento da franxa de memoria non se mostra no depurador clásico; cómpre executar o depurador do contador de rendemento do hardware e velo por si mesmo e directamente. Pero isto non se pode sospeitar directamente a partir destes tres síntomas. O terceiro síntoma é cando miras o que destacas, pregunta ao perfilador e el responde: "Fixeches mil millóns de filas, pero o GC funcionou gratis". Axiña que isto ocorre, dás conta de que creaches demasiados obxectos e queimaches todo o carril de memoria. Hai unha forma de descubrir isto, pero non é obvio. 

O problema está na estrutura de datos: a estrutura espida subxacente a todo o que ocorre, é demasiado grande, é de 2.7 G no disco, polo que facer unha copia desta cousa é moi indesexable: quere cargala desde o búfer de bytes da rede inmediatamente nos rexistros, para non ler-escribir na liña cinco veces. Desafortunadamente, Java non che ofrece unha biblioteca deste tipo como parte do JDK por defecto. Pero isto é trivial, non? Esencialmente, estas son de 5 a 10 liñas de código que se usarán para implementar o teu propio cargador de cadeas almacenado no búfer, que repite o comportamento da clase de cadeas, mentres que é un envoltorio ao redor do búfer de bytes subxacente. Como resultado, resulta que estás a traballar case coma con cadeas, pero de feito os punteiros ao búfer móvense alí e os bytes en bruto non se copian en ningún lado e, polo tanto, os mesmos búfers son reutilizados unha e outra vez, e o sistema operativo está encantado de asumir por si mesmo as cousas para as que está deseñado, como o dobre búfer oculto destes búfers de bytes, e xa non estás traballando nun fluxo interminable de datos innecesarios. Por certo, entendes que ao traballar con GC, está garantido que cada asignación de memoria non será visible para o procesador despois do último ciclo de GC? Polo tanto, todo isto non pode estar na caché, e entón ocorre un fallo 100% garantido. Cando se traballa cun punteiro, en x86, restar un rexistro da memoria leva 1-2 ciclos de reloxo e, en canto isto ocorre, paga, paga, paga, porque a memoria está toda activada. NOVE cachés – e este é o custo da asignación de memoria. Valor real.

Noutras palabras, as estruturas de datos son o máis difícil de cambiar. E unha vez que te das conta de que escolleches a estrutura de datos incorrecta que matará o rendemento máis adiante, normalmente hai moito traballo por facer, pero se non o fas, as cousas empeorarán. Primeiro de todo, cómpre pensar nas estruturas de datos, isto é importante. O custo principal aquí recae nas estruturas de datos gordas, que comezan a usarse ao estilo de "Copiei a estrutura de datos X na estrutura de datos Y porque me gusta máis a forma de Y". Pero a operación de copia (que parece barata) en realidade desperdicia o ancho de banda da memoria e aí é onde se enterra todo o tempo de execución perdido. Se teño unha cadea xigante de JSON e quero convertela nunha árbore DOM estruturada de POJOs ou algo así, a operación de analizar esa cadea e construír o POJO e, a continuación, acceder de novo a POJO máis tarde, terá como resultado un custo innecesario. non barato. Excepto se corres por POJOs con moita máis frecuencia que por unha corda. En cambio, pode tentar descifrar a cadea e extraer só o que precisa de alí, sen convertela en ningún POJO. Se todo isto ocorre nun camiño desde o que se require o máximo rendemento, non hai POJOs para ti, debes buscar dalgunha maneira directamente na liña.

Por que crear a súa propia linguaxe de programación

Andrew: Dixeches que para entender o modelo de custo, cómpre escribir o teu propio idioma...

Acantilado: Non é unha linguaxe, senón un compilador. Unha linguaxe e un compilador son dúas cousas diferentes. A diferenza máis importante está na túa cabeza. 

Andrew: Por certo, que eu sei, estás experimentando coa creación das túas propias linguas. Para qué?

Acantilado: Porque podo! Estou semixubilado, así que esta é a miña afección. Levo toda a vida implementando as linguas doutras persoas. Tamén traballei moito no meu estilo de codificación. E tamén porque vexo problemas noutros idiomas. Vexo que hai mellores formas de facer cousas coñecidas. E usaríaos. Estou farto de ver problemas en min mesmo, en Java, en Python, en calquera outro idioma. Agora escribo en React Native, JavaScript e Elm como un pasatempo que non se trata de xubilación, senón de traballo activo. Tamén escribo en Python e, moi probablemente, seguirei traballando na aprendizaxe automática para backends de Java. Hai moitos idiomas populares e todos teñen características interesantes. Cada un é bo ao seu xeito e podes tentar reunir todas estas características. Entón, estou estudando cousas que me interesan, o comportamento da linguaxe, intentando chegar a unha semántica razoable. E ata agora estou conseguindo! Neste momento estou loitando coa semántica da memoria, porque quero telo como en C e Java, e obter un modelo de memoria forte e unha semántica de memoria para cargas e almacenamentos. Ao mesmo tempo, ten inferencia automática de tipo como en Haskell. Aquí, estou tentando mesturar a inferencia tipo Haskell co traballo de memoria tanto en C como en Java. Isto é o que estiven facendo durante os últimos 2-3 meses, por exemplo.

Andrew: Se constrúes unha lingua que toma mellores aspectos doutras linguas, cres que alguén fará o contrario: colle as túas ideas e utilízaas?

Acantilado: Así é exactamente como aparecen os novos idiomas! Por que Java é semellante a C? Porque C tiña unha boa sintaxe que todos entendían e Java inspirouse nesta sintaxe, engadindo seguridade de tipos, comprobación de límites de matriz, GC, e tamén melloraron algunhas cousas de C. Engadiron as súas propias. Pero inspiráronse bastante, non? Todo o mundo está sobre os ombreiros dos xigantes que viñeron antes que ti, así é como se avanza.

Andrew: Segundo o entendo, o teu idioma estará seguro para a memoria. Pensaches en implementar algo así como un comprobador de préstamos de Rust? Miraches para el, que opinas del?

Acantilado: Ben, levo anos escribindo C, con todo isto malloc e gratuíto, e xestionando manualmente a vida útil. Xa sabes, o 90-95% do tempo de vida controlado manualmente ten a mesma estrutura. E é moi, moi doloroso facelo manualmente. Gustaríame que o compilador che dixese simplemente o que está a suceder alí e o que conseguiches coas túas accións. Para algunhas cousas, o verificador prestado fai isto fóra da caixa. E debería mostrar automaticamente información, entender todo e nin sequera cargarme con presentar esta comprensión. Debe facer polo menos unha análise de escape local, e só se falla, entón necesita engadir anotacións de tipo que describan a vida útil, e tal esquema é moito máis complexo que un verificador de préstamos ou, de feito, calquera comprobador de memoria existente. A elección entre "todo está ben" e "non entendo nada" - non, debe haber algo mellor. 
Entón, como alguén que escribiu moito código en C, creo que ter soporte para o control automático de por vida é o máis importante. Tamén estou farto de canto usa Java a memoria e a principal queixa é o GC. Cando asigna memoria en Java, non recuperará a memoria que era local no último ciclo de GC. Este non é o caso das linguas cunha xestión de memoria máis precisa. Se chamas a malloc, obtén inmediatamente a memoria que normalmente se acababa de usar. Normalmente fai algunhas cousas temporais coa memoria e devólvese inmediatamente. E inmediatamente volve á piscina malloc, e o seguinte ciclo malloc sácao de novo. Polo tanto, o uso real da memoria redúcese ao conxunto de obxectos vivos nun momento determinado, ademais das fugas. E se todo non se filtra dun xeito completamente indecente, a maior parte da memoria acaba en cachés e no procesador, e funciona rapidamente. Pero require moita xestión manual da memoria con malloc e chamada gratuíta na orde correcta, no lugar correcto. Rust pode xestionar isto por si só e, en moitos casos, ofrecer un rendemento aínda mellor, xa que o consumo de memoria redúcese só ao cálculo actual, en lugar de esperar ao seguinte ciclo de GC para liberar memoria. Como resultado, conseguimos unha forma moi interesante de mellorar o rendemento. E bastante poderoso: quero dicir, fixen tales cousas ao procesar datos para fintech, e iso permitiume acelerar unhas cinco veces. Iso é un gran impulso, especialmente nun mundo onde os procesadores non se están facendo máis rápidos e aínda estamos esperando melloras.

Carreira de Enxeñeiro de Performance

Andrew: Tamén me gustaría preguntar sobre as carreiras en xeral. Conseguiches destacar co teu traballo JIT en HotSpot e despois mudácheste a Azul, que tamén é unha empresa JVM. Pero xa traballabamos máis no hardware que no software. E entón pasaron de súpeto ao Big Data e ao Machine Learning, e despois á detección de fraudes. Como pasou isto? Son áreas de desenvolvemento moi diferentes.

Acantilado: Levo bastante tempo programando e conseguín tomar moitas clases diferentes. E cando a xente di: "¡Oh, ti es o que fixo JIT para Java!", sempre é divertido. Pero antes diso, estiven traballando nun clon de PostScript, a linguaxe que Apple utilizou antes para as súas impresoras láser. E antes fixen unha implementación da linguaxe Forth. Creo que un tema común para min é o desenvolvemento de ferramentas. Toda a miña vida estiven facendo ferramentas coas que outras persoas escriben os seus programas xeniais. Pero tamén estiven implicado no desenvolvemento de sistemas operativos, controladores, depuradores a nivel de núcleo, linguaxes para o desenvolvemento de SO, que comezaron de forma trivial, pero co paso do tempo fíxose cada vez máis complexo. Pero o tema principal segue sendo o desenvolvemento de ferramentas. Unha gran parte da miña vida transcorreu entre Azul e Sun, e tratábase de Java. Pero cando entrei en Big Data e Machine Learning, volvín pórme o sombreiro de fantasía e dixen: "Oh, agora temos un problema non trivial, e hai moitas cousas interesantes e hai xente que está facendo". Este é un gran camiño de desenvolvemento a seguir.

Si, encántame moito a informática distribuída. O meu primeiro traballo foi como estudante en C, nun proxecto publicitario. Esta distribuíuse computando en chips Zilog Z80 que recollían datos para OCR analóxico, producidos por un analizador analóxico real. Foi un tema chulo e completamente tolo. Pero houbo problemas, algunha parte non se recoñecía correctamente, polo que había que sacar unha foto e mostrarlla a unha persoa que xa sabía ler cos ollos e informar o que dicía, e polo tanto había traballos con datos, e estes traballos. tiñan a súa propia lingua. Había un backend que procesaba todo isto - Z80s funcionando en paralelo con terminais vt100 funcionando - un por persoa, e había un modelo de programación paralela no Z80. Algunha peza de memoria común compartida por todos os Z80 dentro dunha configuración en estrela; Tamén se compartiu o backplane e a metade da RAM compartiuse dentro da rede e outra metade era privada ou ía a outra cousa. Un sistema distribuído paralelo significativamente complexo con memoria compartida... semicompartida. Cando foi isto... nin me lembro, nalgún lugar a mediados dos 80. Hai bastante tempo. 
Si, supoñamos que hai 30 anos hai bastante tempo. Os problemas relacionados coa computación distribuída existen dende hai moito tempo, a xente leva moito tempo en guerra contra Beowulf- clusters. Estes clústeres parecen... Por exemplo: hai Ethernet e o teu x86 rápido está conectado a esta Ethernet, e agora queres obter memoria compartida falsa, porque ninguén podía facer codificación de computación distribuída entón, era demasiado difícil e, polo tanto, non era memoria compartida falsa con páxinas de memoria de protección en x86, e se escribiu nesta páxina, dixémoslles a outros procesadores que se acceden á mesma memoria compartida, deberían cargala desde ti e, polo tanto, algo así como un protocolo para admitir apareceu a coherencia da caché e software para iso. Concepto interesante. O verdadeiro problema, por suposto, era outro. Todo isto funcionou, pero axiña tivo problemas de rendemento, porque ninguén entendía os modelos de rendemento a un nivel suficientemente bo: que patróns de acceso á memoria había, como asegurarse de que os nodos non facían ping continuamente entre si, etc.

O que se me ocorreu en H2O é que son os propios desenvolvedores os responsables de determinar onde se oculta o paralelismo e onde non. Ocorréuseme cun modelo de codificación que facía que escribir código de alto rendemento fose sinxelo e sinxelo. Pero escribir código de execución lenta é difícil, parecerá malo. Debes tentar seriamente escribir código lento, terás que usar métodos non estándar. O código de freada é visible a primeira vista. Como resultado, adoita escribir código que se executa rápido, pero tes que descubrir que facer no caso da memoria compartida. Todo isto está ligado a grandes matrices e o comportamento alí é semellante ás grandes matrices non volátiles en Java paralelo. Quero dicir, imaxina que dous fíos escriben nunha matriz paralela, un deles gaña e o outro, en consecuencia, perde, e non sabes cal é cal. Se non son volátiles, entón a orde pode ser o que queiras, e isto funciona moi ben. A xente preocúpase moito pola orde das operacións, colocan os volátiles nos lugares correctos e esperan problemas de rendemento relacionados coa memoria nos lugares correctos. En caso contrario, simplemente escribirían código en forma de bucles de 1 a N, onde N son algúns billóns, coa esperanza de que todos os casos complexos se fagan paralelos automaticamente, e non funciona alí. Pero en H2O isto non é nin Java nin Scala; podes consideralo "Java menos menos" se queres. Este é un estilo de programación moi claro e é semellante a escribir código simple C ou Java con bucles e matrices. Pero ao mesmo tempo, a memoria pódese procesar en terabytes. Aínda uso H2O. Eu úsoo de cando en vez en diferentes proxectos, e aínda é o máis rápido, ducias de veces máis rápido que os seus competidores. Se estás facendo Big Data con datos columnares, é moi difícil superar a H2O.

Retos técnicos

Andrew: Cal foi o teu maior reto en toda a túa carreira?

Acantilado: Estamos discutindo a parte técnica ou non técnica do tema? Eu diría que os maiores retos non son técnicos. 
En canto aos retos técnicos. Simplemente os derrotei. Nin sequera sei cal foi o máis grande, pero houbo outros bastante interesantes que levaron bastante tempo, loita mental. Cando fun a Sun, estaba seguro de que faría un compilador rápido, e unha morea de persoas maiores dixeron en resposta que nunca tería éxito. Pero seguín este camiño, escribín un compilador no asignador de rexistros e foi bastante rápido. Era tan rápido como o C1 moderno, pero o asignador era moito máis lento daquela e, en retrospectiva, era un gran problema de estrutura de datos. Necesitaba para escribir un asignador de rexistro gráfico e non entendía o dilema entre a expresividade do código e a velocidade, que existía naquela época e era moi importante. Resultou que a estrutura de datos adoita exceder o tamaño da caché en x86 daquela época e, polo tanto, se inicialmente asumín que o asignador de rexistros funcionaría nun 5-10 por cento do tempo total de jitter, entón en realidade resultou ser 50 por cento.

Co paso do tempo, o compilador fíxose máis limpo e máis eficiente, deixou de xerar código terrible en máis casos e o rendemento comezou a parecerse cada vez máis ao que produce un compilador C. A menos que, por suposto, escribas algunha merda que nin sequera C acelera. . Se escribes código como C, obterás un rendemento como C en máis casos. E canto máis avanzabas, máis frecuentemente conseguistes código que coincidía asintóticamente co nivel C, o asignador de rexistros comezaba a parecer algo completo... independentemente de se o teu código funcionase rápido ou lento. Seguín traballando no asignador para que fixera mellores seleccións. Fíxose cada vez máis lento, pero daba cada vez mellor rendemento nos casos en que ninguén máis podía facerlle fronte. Podería mergullarme nun asignador de rexistros, enterrar alí un mes de traballo e, de súpeto, todo o código comezaría a executarse un 5% máis rápido. Isto ocorreu unha e outra vez e o asignador do rexistro converteuse nunha especie de obra de arte: a todos encantábao ou odiaba, e a xente da academia facía preguntas sobre o tema "por que se fai todo así", por que non. exploración de liña, e cal é a diferenza. A resposta segue a ser a mesma: un asignador baseado na cor de gráficos máis un traballo moi coidadoso co código de memoria intermedia é igual a unha arma da vitoria, a mellor combinación que ninguén pode derrotar. E isto é algo pouco obvio. Todo o demais que fai alí o compilador son cousas bastante estudadas, aínda que tamén foron levadas ao nivel artístico. Sempre fixen cousas que debían converter o compilador nunha obra de arte. Pero nada disto foi algo extraordinario, excepto o asignador de rexistros. O truco é ter coidado Cortar baixo carga e, se isto ocorre (podo explicar con máis detalle se está interesado), isto significa que pode enfilarse de forma máis agresiva, sen o risco de caer nunha torcedura no programa de actuación. Naqueles tempos, había unha morea de compiladores a gran escala, colgados con chuches e asubíos, que tiñan asignadores de rexistro, pero ninguén máis podía facelo.

O problema é que se engades métodos que están suxeitos a inline, aumentando e aumentando a área de inline, o conxunto de valores usados ​​supera ao instante o número de rexistros e tes que cortalos. O nivel crítico adoita chegar cando o asignador se rende, e un bo candidato para un derrame vale outro, venderá algunhas cousas xeralmente salvaxes. O valor de inline aquí é que perde parte da sobrecarga, sobrecarga para chamar e gardar, pode ver os valores dentro e pode optimizalos aínda máis. O custo de inline é que se forma un gran número de valores en directo e, se o teu asignador de rexistros se queima máis do necesario, perde inmediatamente. Polo tanto, a maioría dos repartidores teñen un problema: cando o inline cruza unha determinada liña, todo o mundo comeza a reducirse e a produtividade pódese tirar polo inodoro. Os que implementan o compilador engaden algunhas heurísticas: por exemplo, deixar de inlinear, comezando por un tamaño suficientemente grande, xa que as asignacións arruinarán todo. Así é como se forma unha torcedura no gráfico de rendemento: en liña, en liña, o rendemento crece lentamente, e despois boom! – cae como un gato rápido porque forraches demasiado. Así funcionaba todo antes da chegada de Java. Java require moito máis inline, polo que tiven que facer o meu asignador moito máis agresivo para que se nivelase en lugar de fallar, e se inline demasiado, comeza a derramarse, pero entón aínda chega o momento de "non máis derramar". Esta é unha observación interesante e só me veu da nada, non é obvia, pero pagou ben. Empreguei unha liña agresiva e levoume a lugares onde o rendemento de Java e C traballan en paralelo. Están moi preto: podo escribir código Java que é significativamente máis rápido que o código C e cousas así, pero, de media, no panorama xeral das cousas, son aproximadamente comparables. Creo que parte deste mérito é o asignador de rexistros, que me permite enfilar o máis estúpido posible. Simplemente enlineo todo o que vexo. A pregunta aquí é se o asignador funciona ben, se o resultado é un código que funciona de forma intelixente. Este foi un gran reto: entender todo isto e facelo funcionar.

Un pouco sobre a asignación de rexistros e multinúcleos

Vladimir: Problemas como a asignación de rexistros parecen unha especie de tema eterno e interminable. Pregúntome se houbo algunha vez unha idea que parecía prometedora e logo fracasou na práctica?

Acantilado: Por suposto! A asignación de rexistros é unha área na que se tenta atopar algunhas heurísticas para resolver un problema NP-completo. E nunca podes conseguir unha solución perfecta, non? Isto é simplemente imposible. Mira, a compilación Ahead of Time - tamén funciona mal. A conversa aquí trata sobre algúns casos medios. Sobre o rendemento típico, para que poidas ir medir algo que cres que é un bo rendemento típico; despois de todo, estás traballando para melloralo. A asignación de rexistros é un tema sobre o rendemento. Unha vez tes o primeiro prototipo, funciona e pinta o que fai falta, comeza o traballo de actuación. Hai que aprender a medir ben. Por que é importante? Se tes datos claros, podes mirar diferentes áreas e ver: si, axudou aquí, pero aí rompeu todo! Xorden algunhas boas ideas, engádese novas heurísticas e de súpeto todo comeza a funcionar un pouco mellor de media. Ou non comeza. Tiven unha morea de casos nos que estabamos loitando polo rendemento do cinco por cento que diferenciaba o noso desenvolvemento do asignador anterior. E cada vez que se ve así: nalgún lugar gañas, nalgún lugar perdes. Se tes boas ferramentas de análise de rendemento, podes atopar as ideas perdidas e comprender por que fallan. Quizais paga a pena deixar todo tal e como está, ou quizais asumir un enfoque máis serio para axustar, ou saír e arranxar outra cousa. Son un montón de cousas! Fixen este truco xenial, pero tamén necesito este, e este e este, e a súa combinación total dá algunhas melloras. E os solitarios poden fracasar. Esta é a natureza do traballo de rendemento en problemas NP-completos.

Vladimir: Un ten a sensación de que cousas como pintar en repartidores son un problema que xa está solucionado. Ben, está decidido por ti, a xulgar polo que estás dicindo, entón paga a pena...

Acantilado: Non se resolve como tal. Es ti quen debes convertelo en "solucionado". Hai problemas difíciles e hai que resolvelos. Unha vez feito isto, é hora de traballar na produtividade. Debe abordar este traballo en consecuencia: facer benchmarks, recompilar métricas, explicar situacións nas que, cando regresou a unha versión anterior, o seu antigo hack comezou a funcionar de novo (ou viceversa, deixou de funcionar). E non te rindas ata conseguir algo. Como xa dixen, se hai ideas chulas que non funcionaron, pero no campo da asignación de rexistros de ideas é aproximadamente interminable. Podes, por exemplo, ler publicacións científicas. Aínda que agora esta zona comezou a moverse moito máis lentamente e quedou máis clara que na súa mocidade. Porén, hai infinidade de persoas que traballan neste campo e todas as súas ideas merecen a pena probalas, todas están esperando nas bandas. E non podes dicir o bos que son a menos que os probes. Que ben se integran con todo o demais no teu asignador, porque un asignador fai moitas cousas e algunhas ideas non funcionarán no teu asignador específico, pero noutro asignador farán facilmente. A principal forma de gañar para o asignador é tirar o material lento fóra do camiño principal e obrigalo a dividirse ao longo dos límites dos camiños lentos. Entón, se queres executar un GC, toma o camiño lento, desoptimiza, lanza unha excepción, todas esas cousas, xa sabes que estas cousas son relativamente raras. E son moi raros, comprobei. Fais un traballo extra e elimina moitas restricións nestes camiños lentos, pero non importa porque son lentos e pouco transitados. Por exemplo, un punteiro nulo - nunca ocorre, non? Debe ter varios camiños para cousas diferentes, pero non deben interferir co principal. 

Vladimir: Que opinas dos núcleos múltiples, cando hai miles de núcleos á vez? É isto unha cousa útil?

Acantilado: O éxito da GPU demostra que é bastante útil!

Vladimir: Son bastante especializados. Que pasa cos procesadores de propósito xeral?

Acantilado: Pois ese era o modelo de negocio de Azul. A resposta volveu nunha época na que a xente realmente adoraba o rendemento previsible. Daquela era difícil escribir código paralelo. O modelo de codificación H2O é altamente escalable, pero non é un modelo de propósito xeral. Quizais un pouco máis xeral que cando se usa unha GPU. Estamos a falar da complexidade de desenvolver tal cousa ou da complexidade de usalo? Por exemplo, Azul deume unha lección interesante, un pouco obvia: os cachés pequenos son normais. 

O maior reto da vida

Vladimir: E os retos non técnicos?

Acantilado: O reto máis grande era non ser... amable e agradable coa xente. E como resultado, atopábame constantemente en situacións extremadamente conflitivas. Aqueles nos que sabía que as cousas ían mal, pero non sabía como seguir adiante con eses problemas e non podía manexalos. Moitos problemas a longo prazo, que se prolongaron durante décadas, xurdiron deste xeito. O feito de que Java teña compiladores C1 e C2 é unha consecuencia directa disto. O feito de que non houbese compilación multinivel en Java durante dez anos seguidos tamén é unha consecuencia directa. É obvio que necesitabamos un sistema así, pero non é obvio por que non existía. Tiven problemas cun enxeñeiro... ou cun grupo de enxeñeiros. Érase unha vez, cando empecei a traballar en Sun, estaba... Vale, non só entón, en xeral sempre teño a miña propia opinión sobre todo. E pensei que era certo que podías tomar esta túa verdade e contala de frente. Sobre todo porque tiña unha sorprendente razón a maior parte do tempo. E se non che gusta este enfoque... especialmente se obviamente estás equivocado e fas tonterías... En xeral, poucas persoas poderían tolerar esta forma de comunicación. Aínda que algúns poderían, coma min. Construín toda a miña vida sobre principios meritocráticos. Se me mostras algo mal, de inmediato voume dar a volta e dicir: dixeches tonterías. Ao mesmo tempo, por suposto, pido desculpas e todo iso, notarei os méritos, se é o caso, e tomarei outras medidas correctas. Por outra banda, teño unha sorprendente razón sobre unha porcentaxe sorprendentemente grande do tempo total. E non funciona moi ben nas relacións coa xente. Non trato de ser amable, pero fago a pregunta sen rodeos. "Isto nunca funcionará, porque un, dous e tres". E eles dixeron: "Oh!" Houbo outras consecuencias que probablemente fose mellor ignorar: por exemplo, as que levaron ao divorcio da miña muller e a dez anos de depresión despois diso.

O reto é unha loita coas persoas, coa súa percepción do que se pode ou non se pode facer, o que é importante e o que non. Houbo moitos retos sobre o estilo de codificación. Aínda escribo moito código, e naqueles tempos ata tiven que ir máis lento porque facía demasiadas tarefas paralelas e facíaas mal, en lugar de centrarme nunha. Mirando cara atrás, escribín a metade do código para o comando Java JIT, o comando C2. O seguinte programador máis rápido escribiu a metade de lento, o seguinte a metade de lento, e foi un descenso exponencial. A sétima persoa nesta fila foi moi, moi lenta, iso sempre pasa! Toquei moito código. Mirei quen escribía o que, sen excepción, mirei para o seu código, revisei cada un deles e aínda así seguín escribindo máis eu que ningún deles. Este enfoque non funciona moi ben coa xente. A algunhas persoas non lles gusta isto. E cando non poden manexar, comezan todo tipo de queixas. Por exemplo, unha vez dixéronme que deixase de codificar porque estaba escribindo demasiado código e poñía en perigo o equipo, e todo me pareceu unha broma: amigo, se o resto do equipo desaparece e sigo escribindo código, ti Só perderá medio equipo. Por outra banda, se sigo escribindo código e perdes a metade do equipo, iso parece moi mala xestión. Nunca pensei niso, nunca falei diso, pero aínda estaba nalgún lugar da miña cabeza. O pensamento estaba xirando no fondo da miña mente: "Todos estades bromeando?" Entón, o maior problema era eu e as miñas relacións coa xente. Agora enténdome moito mellor, fun xefe de equipo para programadores durante moito tempo, e agora dígolle directamente á xente: xa sabes, son quen son, e terás que tratar comigo. aquí? E cando comezaron a tratar con iso, todo funcionou. De feito, non son nin malo nin bo, non teño malas intencións nin aspiracións egoístas, é só a miña esencia, e teño que vivir con iso dalgún xeito.

Andrew: Hai pouco todo o mundo comezou a falar sobre a autoconciencia para os introvertidos e as habilidades suaves en xeral. Que podes dicir sobre isto?

Acantilado: Si, esa foi a idea e a lección que aprendín do meu divorcio da miña muller. O que aprendín do divorcio foi entenderme. Así foi como comecei a entender a outras persoas. Comprender como funciona esta interacción. Isto levou a descubrimentos un tras outro. Había conciencia de quen son e do que represento. Que estou a facer: ou estou preocupado coa tarefa, ou estou evitando conflitos ou outra cousa, e este nivel de autoconciencia realmente axuda a manterme controlado. Despois disto todo vai moito máis doado. Unha cousa que descubrín non só en min, senón tamén noutros programadores é a incapacidade de verbalizar os pensamentos cando estás nun estado de estrés emocional. Por exemplo, estás alí sentado codificando, nun estado de fluxo, e despois veñen correndo cara a ti e comezan a berrar histéricamente que algo está roto e que agora tomaranse medidas extremas contra ti. E non podes dicir unha palabra porque estás nun estado de estrés emocional. Os coñecementos adquiridos permítenche prepararse para este momento, sobrevivir a el e pasar a un plan de retirada, despois do cal podes facer algo. Entón, si, cando comezas a darte conta de como funciona todo, é un gran evento que cambia a vida. 
Eu mesmo non atopaba as palabras correctas, pero recordei a secuencia de accións. A cuestión é que esta reacción é tanto física como verbal, e necesitas espazo. Tal espazo, no sentido zen. Isto é exactamente o que hai que explicar e, a continuación, deixar inmediatamente a un lado, afastarse puramente físicamente. Cando calo verbalmente, podo procesar a situación emocionalmente. A medida que a adrenalina chega ao teu cerebro, cambiache ao modo loita ou voo, xa non podes dicir nada, non; agora es un idiota, un enxeñeiro azoutado, incapaz de responder decentemente ou incluso de deter o ataque, e o atacante é libre. para atacar unha e outra vez. Primeiro debes volver ser ti mesmo, recuperar o control, saír do modo "loita ou voo".

E para iso necesitamos espazo verbal. Só espazo libre. Se dis algo, podes dicir exactamente iso e, a continuación, buscar realmente "espazo" para ti: dar un paseo polo parque, encerrarte na ducha, non importa. O principal é desconectar temporalmente desa situación. En canto apagas polo menos uns segundos, o control volve, comezas a pensar con sobriedade. "Vale, non son unha especie de idiota, non fago cousas estúpidas, son unha persoa bastante útil". Unha vez que puideses convencerte, é hora de pasar á seguinte etapa: comprender o que pasou. Atacáronche, o ataque veu de onde non o esperabas, foi unha emboscada deshonesta e vil. Isto é malo. O seguinte paso é entender por que o atacante necesitaba isto. De verdade, por que? Quizais porque el mesmo está furioso? Por que está tolo? Por exemplo, porque se enfocou e non pode aceptar a responsabilidade? Este é o xeito de manexar con coidado toda a situación. Pero isto require marxe de manobra, espazo verbal. O primeiro paso é romper o contacto verbal. Evita a discusión con palabras. Cancela, marcha o máis rápido posible. Se se trata dunha conversación telefónica, simplemente colgue: esta é unha habilidade que aprendín ao comunicarme coa miña ex-muller. Se a conversa non vai ben, só tes que dicir "adeus" e colgar. Do outro lado do teléfono: "bla bla bla", respondes: "si, adeus!" e colgar. Acabas de rematar a conversa. Cinco minutos despois, cando a capacidade de pensar con sensatez volve a ti, arrefriácheste un pouco, faise posible pensar en todo, o que pasou e o que pasará despois. E comeza a formular unha resposta reflexiva, en lugar de só reaccionar por emoción. Para min, o avance na autoconciencia foi precisamente o feito de que en caso de estrés emocional non podo falar. Saír deste estado, pensar e planificar como responder e compensar os problemas: estes son os pasos correctos no caso de non poder falar. O xeito máis sinxelo é fuxir da situación na que se manifesta o estrés emocional e simplemente deixar de participar neste estrés. Despois diso vólvese capaz de pensar, cando pode pensar, vólvese capaz de falar, etc.

Por certo, no xulgado, o avogado contrario intenta facerche isto, agora está claro por que. Porque ten a capacidade de reprimirte ata un estado tal que non podes nin pronunciar o teu nome, por exemplo. Nun sentido moi real, non poderás falar. Se che pasa isto, e se sabes que te atoparás nun lugar onde se desencadean as batallas verbais, nun lugar como o xulgado, podes vir co teu avogado. O avogado defenderáche e deterá o ataque verbal, e farao dun xeito completamente legal, e o espazo Zen perdido volverá contigo. Por exemplo, tiven que chamar á miña familia un par de veces, o xuíz mostrouse moi amable con isto, pero o avogado contrario gritou e berroume, nin sequera puiden botar unha palabra. Nestes casos, usar un mediador funciona mellor para min. O mediador detén toda esta presión que está a verte sobre ti nun fluxo continuo, atopas o espazo Zen necesario e con el volve a capacidade de falar. Este é todo un campo de coñecemento no que hai moito que estudar, moito que descubrir dentro de ti, e todo isto convértese en decisións estratéxicas de alto nivel que son diferentes para as diferentes persoas. Algunhas persoas non teñen os problemas descritos anteriormente; normalmente, as persoas que son vendedores profesionais non os teñen. Todas estas persoas que se ganan a vida coas palabras: cantantes famosos, poetas, líderes relixiosos e políticos, sempre teñen algo que dicir. Non teñen tales problemas, pero eu si.

Andrew: Foi... inesperado. Xenial, xa falamos moito e toca rematar esta entrevista. Sen dúbida reunirémonos na conferencia e poderemos continuar este diálogo. Vémonos en Hydra!

Podes continuar a conversa con Cliff na conferencia Hydra 2019, que se celebrará do 11 ao 12 de xullo de 2019 en San Petersburgo. Virá cun informe "A experiencia Azul Hardware Transactional Memory". As entradas pódense mercar na páxina web oficial.

Fonte: www.habr.com

Engadir un comentario