"Un error típico: comparar sin pensar todo en una fila": una entrevista con Andrey Akinshin sobre la evaluación comparativa



El año pasado, Andrei Akinshin (Dreamwalker) se publicó el libro "Pro .NET Benchmarking" : un trabajo detallado sobre benchmarking, útil tanto para desarrolladores de .NET como para especialistas de TI en otras áreas.

Cuando quedaban un par de meses antes de su lanzamiento, celebramos la conferencia DotNext 2019 Piter, donde en la transmisión en línea le preguntamos a Andrei sobre el libro y, en general, sobre la evaluación comparativa. Parece que desde entonces esta entrevista debería haberse quedado obsoleta: allí hablan sobre el libro en el tiempo futuro, y ahora ya son seis meses. Pero en los últimos seis meses, la humanidad no ha decidido de otra manera tomar el percentil 99, por lo que para todos los que pueden usar la evaluación comparativa, las respuestas de Andrei todavía tienen mucha relevancia e interés.

El realizarásobre el futuro de DotNext con el tema "Hablemos del análisis de rendimiento", es decir, no de escribir puntos de referencia, sino de analizar los valores que recopilaron. En este momento, Andrey está estudiando cientos de artículos sobre estadísticas matemáticas para informarle sobre los métodos más adecuados para el análisis de rendimiento en la vida real. En el libro, también se presta atención a dicho análisis, y en una entrevista, Andrey simplemente explicó su importancia. Por lo tanto, en anticipación de un nuevo informe, abrimos un video de entrevista para todos, e hicimos una transcripción de texto específicamente para Habr: ahora no solo se puede ver, sino también leer.


Lo principal del libro.


- Damos la bienvenida nuevamente a los espectadores de la transmisión de DotNext. Esta vez, Andrey Akinshin está con nosotros.

- ¡Hola a todos!

- Ahora la principal noticia relacionada con usted es el anuncio del libro, que se lanzará en septiembre ...

- Si todo va bien, se lanzará a fines de junio.

Aquí debe comprender cómo funcionan los plazos. Son los más extremos, que no pueden ser violados en ningún caso. En Amazon, el libro ahora tiene una fecha de lanzamiento de alrededor del 23 de agosto. Y si cambia esta fecha, se aplicarán todo tipo de sanciones, Amazon no estará contento. Y si el libro sale antes, bueno, bien.

Por lo tanto, realmente espero que si no hay problemas en ningún lado, sea posible leer en junio. Y así, a finales de agosto es la fecha límite. También trabajas en TI, por lo que entiendes cómo funcionan estas cosas.

- La mayoría de la audiencia probablemente ya escuchó sobre el libro. Pero para aquellos que no saben, comencemos con una historia sobre ella.

- El libro se llama "Benchmarking Pro .NET" . Ella es de la serie Apress, la misma en la que se lanzó recientemente el libro Pro .NET Memory Management de Conrad Coconut . Y se publicó el libro de Sasha Goldstein "Pro .NET Performance" . Probablemente hayas escuchado sobre esto, se juega de vez en cuando en DotNext. Y en la misma serie viene mi libro. Se trata de cómo hacer benchmarking de principio a fin.

Traté de cubrir una variedad de aspectos, a partir de las estadísticas, hay un capítulo separado al respecto. Y no es la forma en que nos enseñaron en la universidad, no tengo un solo ejemplo sobre "bolas que se ponen en cajas". Concéntrese en lo que puede ser útil durante la evaluación comparativa: métricas, desviación estándar, errores estándar, todo tipo de intervalos de confianza y cómo interpretarlos. Es decir, estamos hablando de lo siguiente: si el BenchmarkDotNet condicional le dio un millón de números diferentes, ¿qué debo hacer con ellos? Se dan recomendaciones prácticas sobre cómo interpretar estos datos y sacar conclusiones.

Hay un capítulo, por ejemplo, sobre puntos de referencia vinculados a la CPU y sobre puntos de referencia vinculados a la memoria. Hay muchos estudios de casos diferentes con ejemplos de cómo puede escribir un punto de referencia para 3-4 líneas y al mismo tiempo dispararse en el pie debido a algunos efectos de microarquitectura de los procesadores Intel modernos.

Y hay un capítulo sobre análisis de rendimiento y pruebas de rendimiento. La evaluación comparativa es buena como un experimento separado, pero muchas personas quieren poner puntos de referencia en CI, conducirlos todo el tiempo en algún servidor (idealmente el mismo), recopilar datos, por ejemplo, para detectar degradaciones del rendimiento. Por lo tanto, hay un capítulo sobre cómo trabajar con dichos datos y escribir diferentes tipos de pruebas de rendimiento (hay muchas de ellas diferentes). Cuál es, por ejemplo, la diferencia entre las pruebas para un arranque en frío, para un arranque en caliente, cómo procesar gráficos, cómo procesar conjuntos de datos completos.

En uno de los últimos DotNext hablésobre análisis de rendimiento, habló sobre diferentes métodos de búsqueda de anomalías de rendimiento. La degradación no es el único problema que puede surgir. Por ejemplo, hay distribuciones multimodales cuando el punto de referencia funciona durante un segundo o diez. En un producto grande (especialmente multihilo), tales casos seguramente lo serán, y generalmente ocultan el problema. Incluso si no se trata de pruebas de rendimiento que se ejecutan en las máquinas correspondientes, sino de las pruebas habituales, el diablo sabe cómo "temblar" y dar mucha variación, entonces si recopila y analiza todos estos datos, puede encontrar muchas pruebas con tales problemas.

En general, hay una amplia gama de tareas muy interesantes en la evaluación comparativa, y las arreglé cuidadosamente en los estantes. Pero traté de hacerlo lo más práctico posible, de modo que no se tratara solo de una teoría, sino de un conocimiento que podría tomar y utilizar en mi producto en la producción.

Benchmarking sutilezas


- Recuerdo la frase que leí en alguna parte que, para cualquier resultado de referencia en Internet, puede encontrar la interpretación incorrecta de este resultado. ¿Cuánto estás de acuerdo con esta frase?

- Totalmente de acuerdo. Se habla mucho sobre puntos de referencia válidos e inválidos, pero si se mira a vista de pájaro, si de alguna manera midió algo, recopiló al menos algunas métricas de rendimiento y las mostró en un archivo o consola, entonces este es un punto de referencia con un cierto Desde el punto de vista, es válido: algo mide y muestra algunos números. La pregunta principal es cómo interpreta estos números y qué hará a continuación.

Uno de los primeros errores de las personas que están inmersas en el tema de la evaluación comparativa es que van a comparar todo sin hacerse la pregunta "por qué". Bueno, comparamos, medimos, ¿y luego qué? Es muy importante determinar con qué propósito estamos haciendo benchmarking.

Por ejemplo, si queremos hacer una carga de trabajo estable, con la que podamos evaluar el rendimiento de ciertos casos y detectar la degradación del rendimiento, este es un caso. Otro caso: tenemos dos bibliotecas que hacen lo mismo, y estamos por el rendimiento y queremos elegir la más rápida, ¿cómo se compara esto?

Desde el punto de vista de las interpretaciones, cualquiera que conduzca a la decisión comercial correcta puede considerarse bueno. No es necesariamente correcto, pero si tiene éxito, es bueno.

Y hay tal cosa que incluso tengo ejercicios especiales en mi libro. Digamos que hay dos algoritmos, y el ejercicio es el siguiente: primero debe hacer un punto de referencia que muestre que el primer algoritmo es 10 veces más rápido que el segundo, y luego un punto de referencia que muestre lo contrario. Puede jugar con los datos de origen, con el entorno, cambiar Mono a .NET Core o ejecutar Linux en lugar de Windows: hay un millón de opciones de personalización. Y la conclusión es la siguiente: si se propone mostrar que un programa funciona más rápido que otro, lo más probable es que haya una manera de hacerlo.

Por lo tanto, volviendo a su pregunta, es muy difícil trazar una línea entre puntos de referencia válidos e inválidos y dar una definición de inválido (es decir, qué debería estar allí para que reconozcamos que es malo). Y lo mismo con la distinción entre interpretaciones "correctas" e "incorrectas": no puede comprender completamente lo que está sucediendo en el punto de referencia, no puede explicar completamente todos los procesos internos (lo cual no es muy bueno, sería mejor hacerlo, pero puede omitir esta parte, si está muy ocupado), pero al mismo tiempo entienda en general cómo se ve la imagen. Y si logró hacerlo bien (aquí nuevamente la pregunta es qué es "correcto") y llegó a la decisión comercial correcta, entonces está bien hecho.

“Si solo toma y lee su libro cuidadosamente, ¿comenzará a tomar las decisiones“ correctas ”? ¿O hay muchas cosas fuera del alcance del libro que también influyen?

- La evaluación comparativa es un tema que, en mi opinión, solo se puede dominar en la práctica. Sí, en el libro doy muchas metodologías, recomendaciones, describo trampas. En la evaluación comparativa, hay muchos problemas de este tipo que si no los conoce, nunca los adivinará en la vida. Pero si los conoce, esto no garantiza en absoluto que sus puntos de referencia sean correctos. Es decir, este es un kit de herramientas tan mínimo que ayuda a orientarse de alguna manera en el campo.

Puede escribir puntos de referencia normales y pruebas de rendimiento solo si se ocupa sistemáticamente de esta área. La cuadrícula neural en la cabeza se entrena para leer informes de rendimiento: cuando observa las distribuciones obtenidas durante las mediciones de rendimiento, observa las tablas de resumen, por ejemplo, de BenchmarkDotNet (y no solo la columna "Promedio", sino también la desviación estándar ), observa los errores estándar, las características adicionales, como mínimo, como máximo, en un cuantil, en el percentil 99.

Cuando miras todo esto muy, muy, mucho, se acumula un volumen mínimo, lo que te permite realizar inversiones de rendimiento mucho más rápido y ver lo que las personas sin experiencia (incluso si leen mi libro y un millón de publicaciones de blog) no verán debido al hecho de que no tienen experiencia. No verán ningún problema o no podrán interpretar los datos de forma instantánea y correcta.

- En este DotNext en una entrevista con Dmitry Nesteruk (mezastel) dijimos que, por lo general, los libros de TI se vuelven obsoletos rápidamente, pero si escribe sobre patrones de diseño, no todo cambia allí cada año. ¿Y qué hay de la evaluación comparativa: este libro también puede no estar desactualizado durante mucho tiempo, o habría escrito algo más hace dos años?

- Es muy difícil dar una respuesta monosilábica. Hay alguna base, algún material que no se vuelve obsoleto. Las mismas estadísticas: como el percentil 99 se consideró hace dos años, todavía se considera así, y existe la sospecha de que nada cambiará en dos años.

Por cierto, al mismo tiempo, noto: creo que el benchmarking debería ser una disciplina separada. Por alguna razón, históricamente, nadie prestó la debida atención a las mediciones sistemáticas. Bueno, que hay ahi? Lo tomó, encendió el temporizador, apagó el temporizador, miró cuánto había pasado. Y en el libro, según estimaciones preliminares, resultó más de 600 páginas, y todos me preguntan: "¿Qué se podría escribir en 600 páginas?"

Y creo que esto debería ser una disciplina, un área separada de la informática. Y esta es una dirección tan "agnóstica del lenguaje", donde el equipo general permanece correcto y no cambia: esto es lo que la humanidad en su conjunto ha alcanzado. Esto se aplica a cualquier tiempo de ejecución, idiomas, ecosistemas. Pero esta es solo una parte de la respuesta.

Y la otra parte ya está vinculada a las características del tiempo de ejecución, a las características de .NET. En este momento (tenemos mucho sobre esto en el libro), tenemos el .NET Framework, está el .NET Core y Mono. Las mediciones de rendimiento pueden variar en diferentes tiempos de ejecución o incluso en dos versiones adyacentes del mismo tiempo de ejecución. Si toma .NET Core 2.2 y el próximo .NET Core 3.0, algunas cargas de trabajo difieren al igual que el día y la noche. Hacen optimizaciones tan geniales que los escenarios más simples simplemente se aceleran 10 veces, 50 veces.

Está claro que si cambia a la nueva versión de Core, todo el programa no comenzará a funcionar 50 veces más rápido, pero las piezas pequeñas individuales, que a menudo se clasifican en puntos de referencia sintéticos, pueden ser overclockeadas.

Y lo que cambia, cambia principalmente en relación con todas estas versiones, aparecen nuevas optimizaciones. Por ejemplo, las sacudidas escalonadas aparecerán en .NET Core 3.0 . Es decir, el tiempo de ejecución puede generar rápidamente una implementación nativa simple (y no muy efectiva) para el método por código. Y luego, cuando el tiempo de ejecución advierte que llama a este método muchas, muchas veces, pasará un poco más de tiempo en segundo plano y regenerará un código más productivo. Es aproximadamente el hecho de que en Java en HotSpot ya hay muchos años, en el mundo .NET aparecerá en la versión incluida por defecto este año en la segunda mitad del año (nota del editor: le recordamos que la entrevista se realizó en 2019) .

Y para BenchmarkDotNet, es un desafío manejar tales casos normalmente. En el mundo de Java, Alexey Shipilev en su JMHAprendí a manejarlo hace mucho tiempo, pero aún tenemos que hacerlo. Sobre este tema, también, me comunico con los chicos que vieron el tiempo de ejecución. Es decir, necesitaré bolígrafos especiales, API de ellos para tratar correctamente todo.

Estas cosas están cambiando. Pronto tendremos todos los tiempos de ejecución unidos, habrá un .NET 5. Supongo que se cambiará el nombre de alguna manera diferente, que este es un nombre intermedio. Tal vez no será 5, sino 6, porque ya teníamos una versión de .NET Core 5.0.

- Bueno, como sabemos por Windows, no es un problema para Microsoft omitir el número en la versión.

- Si. Ya en el momento de DNXhubo marcos de destino con el quinto .NET Core, ahora "5.0" ya se ha usado mucho donde, hay muchas publicaciones antiguas. Así que no sé, ahora van a hacer el quinto después de la tercera versión, pero me habría perdido no solo los cuatro, sino también los cinco, y habría logrado el sexto de inmediato. Y teniendo en cuenta el hecho de que ahora quieren hacer, en mi opinión, las versiones impares LTS estables, y las pares no muy estables, sería posible obtener inmediatamente siete.

Bueno, este es su dolor de cabeza. Pero es importante que necesite supervisar el desarrollo de los tiempos de ejecución, y es esta parte específica de .NET la que se está volviendo obsoleta, no tan rápido como obsoleta, sino en silencio.

Ya estoy pensando en hacer la segunda edición de un libro donde todo esto se actualiza. Los procesadores Intel tampoco se detienen, se están desarrollando, aparecen nuevas optimizaciones, que también deben manejarse de manera astuta. Skylake presentó muchas sorpresas desagradables, en el mismo BenchmarkDotNet se trabajó mucho para sortear sus complicadas optimizaciones y obtener resultados estables.

Interacción con BenchmarkDotNet y Rider


- Está claro que trabajar en la biblioteca BenchmarkDotNet le ha dado mucha experiencia, por lo que es lógico que haya sido usted quien escribió el libro sobre el tema de la evaluación comparativa. Y aquí surge la pregunta: ¿está el libro de alguna manera vinculado a BenchmarkDotNet, o es "independiente de la herramienta"?

- Traté de hacerla agnóstica de herramientas. Acerca de BenchmarkDotNet, tengo una pequeña sección, y también la uso como ejemplos en mis estudios de caso: cuando necesito mostrar un pequeño efecto microarquitectura, digo "aquí escribiremos un punto de referencia usando BenchmarkDotNet". Solo para no poner un millón de tiras en cada punto de referencia del libro, no hay lugar para escribir por separado la lógica de calentamiento. Ya tenemos una solución lista para usar que hace toda la rutina de referencia para nosotros, y ya no hablaremos de la metodología (hablamos de ello al principio), sino de los efectos a nivel de CPU.

Aquí hay dos casos de uso, y traté de hacer el resto lo más abstracto posible de BenchmarkDotNet. Que el libro fue útil no solo para los desarrolladores de .NET, sino también, por ejemplo, para los desarrolladores de Java. Debido a que todos los mecanismos comunes se transfieren fácilmente a cualquier otra plataforma, es decir, .NET y BenchmarkDotNet se utilizan como una herramienta para ilustrar el concepto.

- ¿Y en la otra dirección fue la influencia? ¿Qué, en el proceso de trabajar en el libro, finalmente entendió lo que necesita hacer en BenchmarkDotNet de esta manera?

- Sí, escribí todo tipo de pequeñas fichas, especialmente para que estén en el libro. Por ejemplo, la detección genial de distribuciones multimodales, de las que ya hablé.

En el buen sentido, cuando analiza los resultados del punto de referencia, siempre debe mirar la distribución, abrir la imagen, estudiar lo que sucedió allí. Pero en la práctica, nadie lo hace. Porque si ejecuto, condicionalmente, 50 puntos de referencia en alguna base de código, y cambio esta base de código 10 veces al día, y cada vez que reinicio el conjunto completo, entonces ni siquiera veré 50 gráficos, por supuesto, tengo que hacerlo perezosamente. Y esto, en general, no tiene sentido, esta no es una tarea humana, es una tarea de ajuste.

BenchmarkDotNet tiene un algoritmo genial que determina automáticamente que la distribución es multimodal y advierte al usuario: “¡Amigo! ¡Mira el cuadro! ¡Todo está mal aquí! Aquí es donde apareció el valor promedio en la columna, ¡no lo mire! ¡No corresponde a nada, mira la tabla!

Y esto se imprime solo en aquellos casos en que es realmente importante no distraer a una persona en gráficos en vano. Allí, un enfoque basado en los llamados valores m de Brendan Gregg, es un ingeniero de rendimiento líder en Netflix.

Pero su enfoque no fue suficiente para mí porque usa histogramas especialmente construidos basados ​​en la distribución. Es decir, se alimenta un histograma a la entrada, se considera un valor n y está mágicamente determinado por él, tenemos una distribución multimodal o no. ¡Y cómo construir histogramas, Brendan Gregg no escribió! Tuve que inventar algún tipo de mi propia bicicleta, que sorprendentemente funcionó bien. Este algoritmo se resume en un libro.

Había bastantes historias así. Escribir un libro directamente me llevó dos años y medio. En general, he estado recopilando contenido durante cinco años, y dos años y medio desde el momento en que firmé un acuerdo con la editorial. Durante estos dos años y medio, gracias al libro, la biblioteca ha sido bombeada de muchas maneras, hay muchas cosas que han aparecido.

- Es difícil de imaginar, pero además del libro y BenchmarkDotNet, en su vida también hay trabajo en Rider, y allí probablemente también sea un punto de referencia. ¿Puedes hablar de esto? Tuviste en Twitter fotos de un macbook en el congelador y al lado del calentador , comprobando cómo afecta el rendimiento: ¿fue por trabajo, por libro o por ambos a la vez?

- Más bien, todos juntos. En jineteUtilizamos BenchmarkDotNet para la inversión de rendimiento individual. Es decir, cuando necesita descubrir la mejor manera de escribir código en alguna pieza crítica de rendimiento, o necesita estudiar cómo diferimos en el comportamiento de una pieza de código en Mono en Linux y en .NET Framework en Windows. Tomamos BenchmarkDotNet, diseñamos un experimento, recopilamos resultados, sacamos conclusiones, tomamos decisiones comerciales, cómo escribir código para que funcione rápidamente en todas partes. Y luego se descarta este punto de referencia.

Es decir, no tenemos puntos de referencia sistemáticos en BenchmarkDotNet que se ejecuten en CI. Pero en cambio, tenemos muchas otras áreas de trabajo de rendimiento. Por ejemplo, una herramienta interna que recopila números de todas las pruebas y busca diferentes anomalías de rendimiento en ellas, las mismas distribuciones multimodales, pruebas con alguna gran desviación estándar y las recopila en un solo tablero.

Otro enfoque en el que hemos estado trabajando durante mucho tiempo pero que no lo hemos hecho son las pruebas de rendimiento confiables. Es decir, queremos hacer un enfoque en el que sea imposible congelar la degradación del rendimiento en la rama maestra.

Y los puntos de referencia clásicos no son muy adecuados, ya que requieren muchos recursos. Es necesario hacer muchas iteraciones para obtener estadísticas normales y de alguna manera trabajar con ellas. Y cuando tiene cientos o miles de pruebas de rendimiento, si ejecuta cada prueba 30 veces, como se esperaba, y esto es para cada almuerzo de cada persona, no hay suficiente hierro.

Por lo tanto, por un lado, quiero hacer la menor cantidad de iteraciones posible (idealmente una, pero una a la vez es muy difícil decir si tiene degradación). Lo peor que puede pasar es falso positivo, cuando no hiciste nada malo, pero el sistema habla de la degradación del rendimiento y no te permite congelar el brunch en master. Si esto sucede, me arrojarán piedras y nadie usará este sistema.

Por lo tanto, condicionalmente, si después de una iteración hay una sospecha de perfdegradación, pero no hay un 100% de confianza, vale la pena hacer la segunda iteración. Después del segundo, puede decidir que todo está bien con nosotros, por casualidad de que algo sucedió. Puede decir que ahora estamos seguros de la degradación del rendimiento y prohibimos la marcha. Y puede decir: "No, dos iteraciones aún no son suficientes, tenemos que pasar a la tercera". Bueno y así sucesivamente.

Y en un pequeño número de iteraciones (una, dos, tres), las pruebas estándar no funcionan en absoluto. Aquí está mi prueba favorita de Mann-Whitney .comienza a funcionar bien cuando tiene al menos cinco iteraciones. Pero llegamos al quinto solo cuando todo está completamente mal. En consecuencia, es necesario desarrollar un conjunto de heurísticas que nunca den falsos positivos, pero al mismo tiempo detectarán degradaciones cuando existan, con el mínimo número posible de iteraciones. Y ahora esta es una tarea bastante difícil para una mezcla de ingeniería de programación y fórmulas matemáticas. Todavía no hemos terminado, pero vamos a esto.

Y sobre el macbook en el refrigerador, esto también es todo por trabajo. Ahora uno de los mini proyectos que hago bastante es el estudio de modelos de estrangulamiento térmico. La situación es la siguiente: cuando el punto de referencia vinculado a la CPU carga mucho el hardware, la temperatura de la CPU aumenta, y cuando alcanza un cierto valor umbral, el procesador Intel o el sistema operativo dice: “¡Ay-aa-aa! ¡Nos estamos sobrecalentando! - y durante algún período de tiempo reduce la frecuencia. Y luego, por ejemplo, se obtienen 2-3 iteraciones, en las cuales la degradación del rendimiento es supuestamente visible. Y estamos como: "¡Oh, oh, oh, oh! ¡Todo está mal! No celebraremos este brunch ". Pero, de hecho, solo tenemos un agente de rendimiento sobrecalentado.

Hay diferentes formas de lidiar con esto. Tenemos nuestra propia sala de servidores con nuestros propios soportes, estamos tratando de proporcionar suficiente refrigeración allí para que no se produzca este estrangulamiento térmico. Pero esto tampoco siempre tiene éxito. Es decir, no podemos congelar por completo a los agentes, no serán mucho de esto, pero de alguna manera tenemos que luchar.

Otra opción es, por ejemplo, apagar el turbo boost para que el procesador nunca supere la frecuencia base. Esto, en consecuencia, reduce la probabilidad de sobrecalentamiento, el procesador ya no está tan caliente. Y en segundo lugar, obtenemos una frecuencia más estable (en un turbo boost a menudo tiembla bastante, y con un turbo boost desactivado en la frecuencia base funciona mucho, y obtienes un resultado mucho más estable).

Y los modelos de estrangulamiento térmico son muy diferentes: en primer lugar, mucho depende del procesador y de la configuración de toda la plancha, y en segundo lugar, del sistema operativo. Por ejemplo, tome una Mac: tenemos muchas pruebas de Mac, porque hay muchos usuarios y no quieren que Rider se desacelere. Y hay un modelo de estrangulamiento térmico muy agresivo.

En los nuevos procesadores Intel que se anunciaron recientemente, hay bromas aún más complejas. Si su temperatura cae por debajo de un cierto valor umbral, como 50 grados, entonces la frecuencia puede saltar incluso más alta que la frecuencia máxima en un turbo boost normal. Es decir, hacen algo como el overclocking dinámico "un poco" a bajas temperaturas. El mismo efecto. Nuestros agentes siguen siendo procesadores condicionalmente antiguos, aún no se han actualizado, pero los geeks a quienes les gusta comprarse lo último pueden avanzar en esto.

Futuro


"Tengo que interrumpirte, porque el tiempo se acaba". Pero para aquellos que están intrigados: ¿vas a escribir una publicación de blog sobre este material?

- Sí, mientras estoy recolectando material, todo es muy interesante allí, un modelo muy complejo de estrangulamiento térmico. Existe, por ejemplo, Power Throttling en Windows, que le permite ahorrar batería y mucho más. Mientras estoy recopilando datos, y luego los combinaré en una publicación de blog, o incluso en un artículo científico, o caerá en la segunda edición del libro.

, . — , , .

DotNext 2020 Piter -. , , , . -, , . -, . — .

Source: https://habr.com/ru/post/undefined/


All Articles