
Com meus cérebros humanitários, sempre pensei desta forma - se um programador sabe como aumentar o desempenho, então é necessário torná-lo mais desempenho. Uma solução produtiva = a solução certa. Uma linguagem de programação pode ser mais lenta do que outra e, se isso acontecer, a linguagem de programação será desperdiçada.
Bem, com certeza - se o desenvolvedor for um especialista em desempenho, ele se afogará por todas essas coisas, mesmo que estejam erradas.
Naturalmente, tudo isso é um absurdo, mas não cabe a mim falar sobre isso. Por isso, Andrei Akinshin, desenvolvedor e matemático, candidato às ciências físicas e matemáticas, mantenedor do BenchmarkDotNet e perfolizador, autor do livro Pro .NET Benchmarking e apenas um engenheiro muito, muito legal, veio ao nosso podcast.
Abaixo estão citações selecionadas.
É impossível prever tudo em benchmarks
Um colega meu recentemente teve o seguinte. Ele programava pela manhã, estava tudo bem com ele, tudo funcionou rápido. Em algum momento, tudo começou a travar - Rider está lento, IDÉIA, o navegador - tudo está lento. Ele não conseguia entender o que estava acontecendo? E então eu percebi. Ele trabalhava em um laptop preto que ficava perto da janela. Estava muito frio pela manhã e o sol nasceu à tarde, o laptop esquentou muito e entrou em controle térmico.
Ele sabe que existe tal coisa, sabe que o ambiente físico pode afetar o desempenho e rapidamente percebeu o que estava acontecendo. Ele tinha um modelo em sua cabeça segundo o qual o mundo funciona, e dentro desse modelo ele descobriu mais ou menos rapidamente o que estava acontecendo.
Ou seja, a habilidade mais importante que pode ser obtida no benchmarking é não saber tudo em absolutamente todos os detalhes - todos os tempos de execução e todo o hardware. O principal é entender como você precisa agir para encontrar o problema, de preferência o mais rápido possível com o mínimo de esforço.
Vou fazer uma analogia com as línguas. Quando você aprende sua primeira linguagem de programação funcional, precisa modificar ligeiramente sua atitude em relação ao mundo - para entender os princípios da programação funcional, como geralmente você precisa pensar. Então, você pega a próxima linguagem funcional X e já tem esses princípios em sua cabeça. Você assiste a alguns hello world e começa a escrever também.
Ao mesmo tempo, você pode não conhecer algumas das nuances do idioma. Você pode não saber como certas construções sintáticas funcionam, mas isso não o incomoda muito. Você se sente confortável e escreve. Enfrentei um comportamento incompreensível - li o manual, descobri, um fato novo entrou facilmente em sua imagem do mundo e você foi além. E você nunca aprenderá todas as nuances de todas as linguagens funcionais do mundo, mas a abordagem geral permanecerá em sua cabeça.
Eu acredito que você precisa atingir um nível semelhante em cada área, e então ir em amplitude.
Em algum ponto do benchmarking, me concentrei especificamente na precisão da medição, nas características de certos tempos de execução, um pedaço de ferro, outra coisa. Então, parei de descobrir a América todas as vezes, e todos os problemas de desempenho começaram a cair nas classes que eu já conhecia. E fui mais longe - na direção da análise de desempenho: o que fazer com os números que medimos. E esta é a área onde ainda não cheguei à beira do conhecimento. Algumas coisas já ficaram claras para mim, mas ainda há muito trabalho pela frente - entender como aplicar tudo isso na prática, quais fórmulas usar, quais não usar, quais abordagens são boas, quais não são.
Benchmarking pelo bem de benchmarking não é a melhor coisa a fazer
Sempre deve haver alguns requisitos de desempenho de negócios, você deve sempre entender o que está buscando. Se você não tem requisitos de negócios, também não faz sentido executar o desempenho. Conseqüentemente, quando há requisitos de negócios, você já começa a entender quais abordagens, pelo menos a olho nu, você pode usar e quais não pode. Se não, vá, compare, verifique - quais abordagens se encaixam em seus requisitos.
E quando você tem um conjunto de algoritmos, opções para escrever código, design e outras coisas, e tudo se encaixa nos requisitos - você já escolhe o que será mais consistente com o resto do projeto, o que reflete suas opiniões sobre a estética, sobre como escrever o código corretamente ...
A grosso modo, se eu tiver um máximo de 10 elementos em uma coleção e houver duas opções - escrever um algoritmo simples para um cubo ou um muito complexo para n * log n - escreverei um algoritmo simples para um cubo que será claro para todos, que será fácil de manter e modificar. Porque eu entendo que ele nunca vai superar minhas limitações de desempenho.
Se você escreveu uma solução lenta para um pequeno conjunto de dados e, em seguida, a usou para um grande conjunto de dados, e ela não teve consequências extremamente ruins (geralmente não) - bem, vamos corrigir isso. Mas na cabeça haverá um modelo de como evitar esses erros no futuro.
Por exemplo, você pode colocar assert bem no início do método para que o número de elementos na coleção não exceda tal e tal número. Então, o próximo programador que acidentalmente tentar usar o seu método verá imediatamente uma exceção e não a usará. Essas coisas vêm com a experiência.
Há outro problema - requisitos de negócios voláteis. Eles vão mudar definitivamente - este é um axioma da nossa realidade, não há como escapar disso. Com a experiência, você será capaz de prever de olho onde os requisitos podem mudar, onde vale a pena estabelecer um bom nível de desempenho, onde a carga pode aumentar.
Enquanto essa intuição não estiver lá, você pode passar por tentativa e erro e ver o que acontece.
Você sempre tem uma troca entre desempenho e beleza
Se você escrever da forma mais eficiente possível, provavelmente, seu código será simplesmente terrível, nojento - e mesmo se você fechar os olhos para a estética, será difícil de manter, bugs sutis aparecerão constantemente, porque a arquitetura é ruim, o código é ruim, tudo está ruim.
Acho que você precisa se concentrar nos requisitos de negócios atuais e escrever o código mais limpo, mais compreensível, bonito e sustentável dentro deles. E no momento em que começa a apertar (ou há a sensação de que vai começar em breve), algo já mudou.
E mesmo que você sempre se concentre exclusivamente no desempenho, não existe código perfeitamente otimizado e com produtividade máxima. Significa tudo - esqueci C #, esqueci todas as belas linguagens. E é melhor escrever em geral em códigos de máquina, porque o Assembler também é limitado na sintaxe. E se você escrever imediatamente nos bytes, terá um aumento de desempenho.
Em alguns casos, o código mais rápido acaba sendo o mais bonito, o mais óbvio, o mais correto. Mas essas compensações surgem inevitavelmente em dezenas e centenas de pequenos momentos. Digamos que exista uma verificação de violação dos limites do array. Você pode concordar que o tempo de execução cuidará de você para verificar os limites do array em todos os lugares, e se você ativar o menos o primeiro elemento, obterá uma exceção e não lerá do pedaço esquerdo de memória.
E por essa confiança que você definitivamente nunca subtrai da parte errada da memória - você paga com um pequeno pedaço de desempenho. Ou seja, usamos o desempenho como recurso para tornar o programa mais estável, compreensível e sustentável.
A linguagem não tem a propriedade de desempenho
Se você vir um artigo em que X é mais rápido do que Y, poderá fechá-lo. A linguagem é uma abstração matemática. Este é um conjunto de regras segundo as quais o programa é compilado. Não tem performance, não tem performance, é algo que existe na sua cabeça e está corporificado em um editor de texto.
O desempenho está disponível para tempos de execução, ambientes, programas específicos e apishe específicos. Quando você leva todos esses fatores em consideração, pode falar sobre desempenho. Mas há uma explosão combinatória, e você não pode dizer que um código nesta linguagem é sempre mais rápido do que outro código nesta, porque novas versões de hardware e tempos de execução estão surgindo. Você nunca passará por todas as combinações possíveis de fatores externos em sua vida. Os apishkas que você usa são fundamentalmente diferentes.
Por exemplo, em uma linguagem condicional nos primeiros estágios de desenvolvimento, eles implementaram um método para classificar com uma bolha. Bem, eu não sei - os caras queriam lançar o lançamento o mais rápido possível, escreveram a classificação mais simples que podiam fazer. Você pegou e usou esse método, e ele acabou sendo mais lento em big data do que em outra linguagem em que o quicksort é feito. Isso significa que você pode falar sobre o desempenho de alguns idiomas? Não. Você pode dizer que este apish em particular desta linguagem neste sistema operacional, neste hardware, nestes ambientes, funciona mais devagar do que outro apish de outra linguagem em outro ambiente. Então você pode dizer. Mas acabará sendo um parágrafo de texto muito longo para formular corretamente.
Convencionalmente, podemos dizer que C ++ é mais rápido na maioria dos casos do que JavaScript. Mas seria mais correto dizer que os programadores C ++ com boa experiência em C ++ que escrevem em C ++ escreverão um programa que provavelmente será mais rápido do que um javascriptor JavaScript escreverá algo que funcionará em um navegador.
Mas também há muitas reservas aqui. Mas e se o cara que escreveu em JavaScript disser que não é, e for para algum tipo de WebAssembly lá ou outra coisa para refazer. Ou encontre no GitHub um superinterpretador / compilador JavaScript que funciona com um subconjunto muito truncado de JS por três construções de sintaxe e meia, mas produz código nativo super rápido.
E aí, se desejar, você pode escrever um código que superará o C ++. Além disso, você pode escrever seu próprio compilador JavaScript, que será projetado para compilar um único programa e superar as "vantagens" em velocidade. E esta é, em princípio, uma opção válida.
Pressão social de um projeto popular de código aberto
Com o crescimento e a popularidade dos projetos, vem um certo nível de responsabilidade. Mas você realmente não tem obrigações. Esse fato nem sempre é fácil de entender, principalmente quando todo tipo de pessoa vem ao GitHub e diz: “Não funciona para mim aqui! Conserte com urgência! Eu realmente preciso que isso funcione. Vá e conserte! " Ou um cara consegue um emprego e eu estou de férias. Três ou quatro dias se passaram, eu nem vi que ele começou alguma coisa ali. Estou descansando em algum lugar, e o cara começa - “Por que diabos você não está me respondendo? Que tipo de comunidade este projeto tem?! Vocês geralmente são pessoas nojentas, têm que fazer coisas ruins com vocês! Perdi meu tempo, escrevi que você está errado e não está fazendo nada a respeito, você está me ignorando há quatro dias! Como isso é possível ?! "
E quanto mais popular o projeto, maior a pressão social das pessoas que acreditam que o código aberto é um lugar onde outras pessoas fazem seu trabalho de graça. Mas na verdade não é.
E agora, quando surge imunidade contra pessoas que querem algo de você, a vida se torna muito mais fácil. Agora eu chego ao BenchmarkDorNet quando tenho tempo e disposição para codificar. Eu sei que há muitos bugs lá. Eles são em sua maioria acríticos e dizem respeito a alguns tipos de casos marginais - em tal e tal ambiente com a última prévia do quinto DotNet, algo em algum lugar não funciona. Bem, ok, deixe não funcionar. Quando estiver com vontade, irei consertar.
Se outras pessoas precisarem, podem consertar sozinhas e enviar uma solicitação de pull - farei uma revisão quando tiver tempo e disposição.
Assista ao podcast completo aqui .