Às vezes, o código de baixa latência precisa ser "feio"?

21

(Destina-se principalmente àqueles que possuem conhecimentos específicos de sistemas de baixa latência, para evitar que as pessoas apenas respondam com opiniões sem fundamento).

Você sente que existe uma troca entre escrever código "bom" orientado a objeto e escrever código de baixa latência muito rápido? Por exemplo, evitando funções virtuais em C ++ / a sobrecarga do polimorfismo etc - reescrevendo código que parece desagradável, mas é muito rápido etc?

É lógico - quem se importa se parecer feio (desde que seja sustentável) - se você precisa de velocidade, precisa de velocidade?

Gostaria de ouvir pessoas que trabalharam nessas áreas.

user997112
fonte
1
@ user997112: O motivo mais próximo é auto-explicativo. Ele diz: "Esperamos que as respostas sejam apoiadas por fatos, referências ou conhecimentos específicos, mas essa pergunta provavelmente solicitará debate, argumentos, pesquisas ou discussão prolongada. Não significa necessariamente que elas estejam corretas, mas esse foi o fim. motivo escolhido pelos três eleitores próximos:
Robert Harvey
Curiosamente, eu diria que a razão pela qual esta questão está atraindo votos próximos é que ela pode ser vista como um discurso obscuro (embora eu não ache que seja).
22712 Robert Harvey
8
Eu vou esticar o pescoço para fora: faço o terceiro voto para fechar como "não construtivo", porque acho que o questionador responde praticamente sua própria pergunta. O código "bonito" que não é executado rápido o suficiente para fazer o trabalho falhou em atender ao requisito de latência. O código "feio", que é executado com rapidez suficiente, pode ser mais mantido por meio de uma boa documentação. Como você mede a beleza ou a feiura é um tópico para outra pergunta.
Blrfl
1
O código fonte do Disruptor da LMAX não é muito feio. Existem algumas partes do modelo de segurança de Java (classe insegura) e algumas modificações específicas de hardware (variáveis ​​preenchidas pela linha de cache), mas é IMO muito legível.
19412 James
5
@ Carson63000, user1598390 e quem mais estiver interessado: Se a pergunta terminar, sinta-se à vontade para perguntar sobre o fechamento em nosso site Meta , não faz muito sentido discutir um fechamento nos comentários, especialmente um fechamento que não aconteceu . Além disso, lembre-se de que todas as perguntas fechadas podem ser reabertas, não é o fim do mundo. Exceto, é claro, se os maias estavam certos; nesse caso, foi bom conhecer todos vocês!
yannis

Respostas:

31

Você sente que existe uma troca entre escrever código "bom" orientado a objeto e escrever código de [muito] baixa latência?

Sim.

É por isso que a frase "otimização prematura" existe. Ele existe para forçar os desenvolvedores a medir seu desempenho e otimizar apenas o código que fará diferença no desempenho, enquanto projeta de maneira sensata sua arquitetura de aplicativos desde o início, para que não caia sob carga pesada.

Dessa forma, na máxima extensão possível, você mantém seu código orientado a objetos, bonito e bem arquitetado, e otimiza apenas com código feio aquelas pequenas porções que importam.

Robert Harvey
fonte
15
"Faça funcionar, depois faça rápido". Essa resposta abrange praticamente tudo o que pensei em dizer ao ler a pergunta.
Carson63000
13
Vou acrescentar "Meça, não adivinhe"
Martijn Verburg
1
Penso que vale a pena colocar alguns elementos em evitar o trabalho básico, à medida que você avança, desde que não ocorra à custa da legibilidade. Manter as coisas concisas, legíveis e fazer apenas as coisas óbvias que eles precisam fazer leva a muitas vitórias indiretas a longo prazo, como outros desenvolvedores que sabem o que diabos fazer com seu código, para que não dupliquem esforços ou façam suposições ruins. sobre como isso funciona.
precisa
1
Em "otimização prematura" - isso ainda se aplica mesmo que o código otimizado seja tão "bom" quanto o código não otimizado. O ponto é não perder tempo com o objetivo de velocidade / o que você não precisa alcançar. De fato, a otimização nem sempre é sobre velocidade e, sem dúvida, existe uma otimização desnecessária para a "beleza". Seu código não precisa ser uma excelente obra de arte para ser legível e sustentável.
precisa saber é o seguinte
Eu segundo @ Steve314. Sou o líder de desempenho de um produto e frequentemente encontro códigos extremamente complicados, cuja origem posso rastrear até algum tipo de otimização de desempenho. A simplificação desse código geralmente revela uma melhoria significativa no desempenho. Um exemplo desse tipo se transformou em uma melhoria de desempenho de 5x quando o simplifiquei (redução líquida de milhares de linhas de código). Claramente, ninguém teve tempo para medir e simplesmente fez a otimização prematura do que eles pensavam que provavelmente seria um código lento .
Brandon
5

Sim, o exemplo que eu dou não é C ++ vs. Java, mas é Assembly vs. COBOL, pois é o que eu sei.

Os dois idiomas são muito rápidos, mas mesmo o COBOL, quando compilado, tem muito mais instruções que são colocadas no conjunto de instruções que não necessariamente precisam estar lá versus escrever essas instruções no Assembly.

A mesma idéia pode ser aplicada diretamente à sua questão de escrever "código de aparência feia" vs. usar herança / polimorfismo em C ++. Eu acredito que é necessário escrever um código feio, se o usuário final precisar de prazos de transação de menos de um segundo, é nosso trabalho como programadores dar a eles que não importa como isso aconteça.

Dito isto, o uso liberal de comentários aumenta muito a funcionalidade e a capacidade de manutenção do programador, não importa quão feio seja o código.

Josh Tollefson
fonte
3

Sim, existe uma troca. Com isso, quero dizer que o código mais rápido e mais feio não é necessário melhor - os benefícios quantitativos do "código rápido" precisam ser ponderados com base na complexidade de manutenção das alterações de código necessárias para atingir essa velocidade.

O trade-off vem do custo do negócio. O código mais complexo requer programadores mais qualificados (e programadores com um conjunto de habilidades mais focado, como aqueles com arquitetura de CPU e conhecimento de design), leva mais tempo para ler e entender o código e corrigir bugs. O custo comercial do desenvolvimento e manutenção desse código pode estar entre 10 e 100 vezes mais do que o código normalmente escrito.

Esse custo de manutenção é justificável em alguns setores , nos quais os clientes estão dispostos a pagar um prêmio muito alto por software muito rápido.

Algumas otimizações de velocidade geram melhor retorno sobre o investimento (ROI) do que outras. Nomeadamente, algumas técnicas de otimização podem ser aplicadas com menor impacto na manutenção do código (preservando a estrutura de nível superior e a legibilidade de nível inferior) em comparação com o código normalmente escrito.

Assim, o proprietário de uma empresa deve:

  • Veja os custos e benefícios,
  • Faça medições e cálculos
    • Peça ao programador para medir a velocidade do programa
    • Peça ao programador que estime o tempo de desenvolvimento necessário para a otimização
    • Faça uma estimativa própria sobre o aumento da receita de software mais rápido
    • Os arquitetos de software ou os gerentes de controle de qualidade medem qualitativamente os inconvenientes da reduzida intuitividade e legibilidade do código-fonte
  • E priorize os frutos mais baixos da otimização de software.

Essas compensações são altamente específicas às circunstâncias.

Isso não pode ser decidido da melhor maneira possível sem a participação de gerentes e proprietários de produtos.

Estes são altamente específicos para plataformas. Por exemplo, CPUs de desktop e móveis têm considerações diferentes. Aplicativos de servidor e cliente também têm considerações diferentes.


Sim, geralmente é verdade que o código mais rápido parece diferente do código normalmente escrito. Qualquer código diferente levará mais tempo para ler. Se isso implica feiura está nos olhos de quem vê.

As técnicas com as quais tenho alguma exposição são: (sem tentar reivindicar nenhum nível de especialização) otimização de vetor curto (SIMD), paralelismo detalhado de tarefas, pré-alocação de memória e reutilização de objetos.

O SIMD geralmente tem impactos graves na legibilidade de baixo nível, mesmo que normalmente não exija alterações estruturais de nível superior (desde que a API seja projetada com a prevenção de gargalos em mente).

Alguns algoritmos podem ser transformados em SIMD facilmente (o vetor embaraçoso). Alguns algoritmos requerem mais reorganizações de computação para usar o SIMD. Em casos extremos, como o paralelismo SIMD da frente de onda, algoritmos inteiramente novos (e implementações patenteáveis) precisam ser escritos para tirar vantagem.

A paralelização de tarefas refinada exige a reorganização dos algoritmos nos gráficos de fluxo de dados e aplica repetidamente a decomposição funcional (computacional) ao algoritmo até que nenhum benefício adicional na margem possa ser obtido. Os estágios decompostos geralmente são encadeados com o estilo de continuação, um conceito emprestado da programação funcional.

Por decomposição funcional (computacional), algoritmos que poderiam ter sido normalmente escritos em uma sequência linear e conceitualmente clara (linhas de código que são executáveis ​​na mesma ordem em que são escritas) precisam ser divididos em fragmentos e distribuídos em várias funções ou classes. (Veja a objetificação do algoritmo, abaixo.) Essa alteração impedirá enormemente outros programadores que não estão familiarizados com o processo de design de decomposição que deu origem a esse código.

Para tornar esse código sustentável, os autores desse código devem escrever documentações elaboradas do algoritmo - muito além do tipo de comentário de código ou diagramas UML feitos para código normalmente escrito. É semelhante à maneira como os pesquisadores escrevem seus trabalhos acadêmicos.


Não, o código rápido não precisa estar em contradição com a orientação a objetos.

Em outras palavras, é possível implementar um software muito rápido que ainda é orientado a objetos. No entanto, na extremidade inferior dessa implementação (no nível das porcas e parafusos, onde ocorre a maior parte da computação), o design do objeto pode se desviar significativamente dos designs obtidos do OOD (Oriented Object Design). O design de nível inferior é voltado para a objetificação de algoritmos.

Alguns benefícios da programação orientada a objetos (OOP), como encapsulamento, polimorfismo e composição, ainda podem ser obtidos a partir da objetificação de algoritmos de baixo nível. Essa é a principal justificativa para o uso de OOP nesse nível.

A maioria dos benefícios do design orientado a objetos (OOD) são perdidos. Mais importante ainda, não há intuitividade no design de baixo nível. Um colega programador não pode aprender a trabalhar com o código de nível inferior sem primeiro entender completamente como o algoritmo foi transformado e decomposto em primeiro lugar, e esse entendimento não é obtido com o código resultante.

rwong
fonte
2

Sim, às vezes o código precisa ser "feio" para fazê-lo funcionar no tempo necessário, embora todo o código não precise ser feio. O desempenho deve ser testado e analisado antes para encontrar os bits de código que precisam ser "feios" e essas seções devem ser anotadas com um comentário para que futuros desenvolvedores saibam o que é propositadamente feio e o que é apenas preguiça. Se alguém estiver escrevendo muitos códigos mal projetados, alegando motivos de desempenho, faça-o provar.

A velocidade é tão importante quanto qualquer outro requisito de um programa, dar correções erradas a um míssil guiado é equivalente a fornecer as correções corretas após o impacto. A manutenção é sempre uma preocupação secundária ao código de trabalho.

Ryathal
fonte
2

Alguns dos estudos dos quais vi extratos indicam que o código limpo e fácil de ler geralmente é mais rápido do que o código mais complexo e difícil de ler. Em parte, isso se deve à maneira como os otimizadores são projetados. Eles tendem a ser muito melhores na otimização de uma variável em um registro, do que fazendo o mesmo com um resultado intermediário de um cálculo. Seqüências longas de tarefas usando um único operador que levam ao resultado final podem ser otimizadas melhor do que uma equação longa e complicada. Otimizadores mais recentes podem ter reduzido a diferença entre código limpo e complicado, mas duvido que tenham eliminado.

Outras otimizações, como desenrolamento de loop, podem ser adicionadas de maneira limpa, quando necessário.

Qualquer otimização adicionada para melhorar o desempenho deve ser acompanhada de um comentário apropriado. Isso deve incluir uma declaração de que foi adicionado como uma otimização, de preferência com medidas de desempenho antes e depois.

Eu descobri que a regra 80/20 se aplica ao código que otimizei. Como regra geral, não otimizo nada que não esteja demorando pelo menos 80% do tempo. Em seguida, viso (e geralmente alcanço) um aumento de 10 vezes no desempenho. Isso melhora o desempenho em cerca de 4 vezes. A maioria das otimizações implementadas não tornou o código significativamente menos "bonito". Sua milhagem pode variar.

BillThor
fonte
2

Se feio, você quer dizer difícil de ler / entender no nível em que outros desenvolvedores o reutilizarão ou precisarão entendê-lo, então eu diria que código elegante e fácil de ler quase sempre resultará em uma rede. ganho de desempenho a longo prazo em um aplicativo que você deve manter.

Caso contrário, às vezes há uma vitória de desempenho suficiente para fazer valer a pena colocar uma coisa feia em uma caixa bonita com uma interface matadora, mas, na minha experiência, esse é um dilema bastante raro.

Pense em evitar o trabalho básico à medida que avança. Guarde os truques misteriosos para quando um problema de desempenho realmente se apresentar. E se você precisar escrever algo que alguém possa entender apenas através da familiarização com a otimização específica, faça o que puder para, pelo menos, tornar o feio fácil de entender a partir da reutilização do seu ponto de vista do código. Código que executa miseravelmente raramente o faz porque os desenvolvedores estavam pensando demais sobre o que o próximo cara herdaria, mas se alterações frequentes são a única constante de um aplicativo (a maioria dos aplicativos da web na minha experiência), código rígido / inflexível difícil de modificar está praticamente implorando por bagunças em pânico para começar a aparecer em toda a sua base de código. Limpo e enxuto é melhor para o desempenho a longo prazo.

Erik Reppen
fonte
Eu gostaria de sugerir duas mudanças: (1) Há lugares onde a velocidade é necessária. Nesses lugares, acho que vale mais a pena facilitar a compreensão da interface do que facilitar a compreensão da implementação , porque a última pode ser muito mais difícil. (2) "O código que executa miseravelmente raramente o faz ...", que eu gostaria de reformular como "Uma forte ênfase na elegância e simplicidade do código raramente é a causa do desempenho miserável. O primeiro é ainda mais importante se mudanças frequentes são antecipados, ... "
rwong 20/12/12
A implementação foi uma má escolha de palavras em uma conversa OOPish. Eu quis dizer isso em termos de facilidade de reutilização e edição. # 2, acabei de adicionar uma frase para estabelecer que 2 é essencialmente o ponto que eu estava fazendo.
amigos estão dizendo sobre erik
1

Complexo e feio não são a mesma coisa. Código que possui muitos casos especiais, otimizado para absorver toda a última gota de desempenho, e que se assemelha a um emaranhado de conexões e dependências, pode de fato ser muito cuidadosamente projetado e bonito quando você o entender. De fato, se o desempenho (medido em termos de latência ou outra coisa) é importante o suficiente para justificar código muito complexo, o código deve ser bem projetado. Caso contrário, não poderá ter certeza de que toda essa complexidade é realmente melhor do que uma solução mais simples.

Código feio, para mim, é um código desleixado, mal considerado e / ou desnecessariamente complicado. Eu não acho que você desejaria algum desses recursos no código que precisam ser executados.

Caleb
fonte
1

Você sente que existe uma troca entre escrever código "bom" orientado a objeto e escrever código de baixa latência muito rápido? Por exemplo, evitando funções virtuais em C ++ / a sobrecarga do polimorfismo etc - reescrevendo código que parece desagradável, mas é muito rápido etc?

Eu trabalho em um campo que é um pouco mais focado na taxa de transferência do que na latência, mas é muito crítico para o desempenho e eu diria "meio que" .

No entanto, um problema é que muitas pessoas entendem completamente suas noções de desempenho. Os iniciantes geralmente entendem tudo errado e todo o seu modelo conceitual de "custo computacional" precisa ser reformulado, com apenas a complexidade algorítmica sendo a única coisa que eles podem acertar. Intermediários entendem muitas coisas erradas. Especialistas entendem algumas coisas erradas.

Medir com ferramentas precisas que podem fornecer métricas como falhas de cache e previsões incorretas de ramificações é o que mantém todas as pessoas com qualquer nível de conhecimento em campo sob controle.

Medir é também o que aponta o que não otimizar . Os especialistas costumam gastar menos tempo otimizando do que os novatos, já que estão otimizando pontos de acesso medidos verdadeiros e não tentando otimizar facadas selvagens no escuro com base em palpites sobre o que poderia ser lento (o que, de forma extrema, poderia tentar micro-otimizar apenas sobre todas as outras linhas da base de código).

Projetando para o desempenho

Com isso de lado, a chave para o design de desempenho vem da parte do design , como no design de interface. Um dos problemas com a inexperiência é que tende a haver uma mudança precoce nas métricas absolutas de implementação, como o custo de uma chamada de função indireta em algum contexto generalizado, como se o custo (que é melhor compreendido em sentido imediato do ponto de vista de um otimizador) ponto de vista de ramificação) é um motivo para evitá-lo por toda a base de código.

Os custos são relativos . Embora exista um custo para uma chamada de função indireta, por exemplo, todos os custos são relativos. Se você está pagando esse custo uma vez para chamar uma função que percorre milhões de elementos, se preocupar com esse custo é como passar horas pechinchando moedas de um centavo para a compra de um produto de um bilhão de dólares, apenas para concluir que não o comprará porque era um centavo muito caro.

Design de interface mais grosseiro

O aspecto do design de interface do desempenho geralmente busca elevar esses custos a um nível mais grosso. Em vez de pagar os custos de abstração de tempo de execução para uma única partícula, por exemplo, podemos elevar esse custo ao nível do sistema / emissor de partículas, tornando efetivamente uma partícula em um detalhe de implementação e / ou simplesmente dados brutos dessa coleção de partículas.

Portanto, o design orientado a objetos não precisa ser incompatível com o design para desempenho (latência ou taxa de transferência), mas pode haver tentações em uma linguagem focada nele para modelar objetos granulares cada vez menores, e o otimizador mais recente não pode Socorro. Ele não pode fazer coisas como unir uma classe que representa um único ponto de uma maneira que produz uma representação SoA eficiente para os padrões de acesso à memória do software. Uma coleção de pontos com o design de interface modelado no nível de grosseria oferece essa oportunidade e permite a iteração em direção a soluções cada vez mais otimizadas, conforme necessário. Esse design foi desenvolvido para memória em massa *.

* Observe o foco na memória aqui e não nos dados , pois trabalhar em áreas críticas de desempenho por muito tempo tenderá a mudar sua visão dos tipos e estruturas de dados e ver como eles se conectam à memória. Uma árvore de pesquisa binária não se torna mais apenas sobre complexidade logarítmica em casos como pedaços de memória possivelmente díspares e pouco amigáveis ​​ao cache para nós de árvore, a menos que auxiliados por um alocador fixo. A exibição não descarta a complexidade algorítmica, mas não a vê mais independentemente dos layouts de memória. Também se começa a ver as iterações de trabalho como sendo mais sobre as iterações de acesso à memória. *

Muitos projetos críticos para o desempenho podem realmente ser muito compatíveis com a noção de design de interface de alto nível que é fácil para os seres humanos entenderem e usarem. A diferença é que "alto nível" nesse contexto seria sobre agregação em massa de memória, uma interface modelada para coleções de dados potencialmente grandes e com uma implementação oculta que pode ser de nível bastante baixo. Uma analogia visual pode ser um carro realmente confortável, fácil de dirigir, manusear e muito seguro, na velocidade do som, mas se você abrir o capô, haverá pequenos demônios que cospem fogo no interior.

Com um design mais grosso, também tende a ser uma maneira mais fácil de fornecer padrões de bloqueio mais eficientes e explorar o paralelismo no código (multithreading é um assunto exaustivo que eu vou pular aqui).

Conjunto de memórias

Um aspecto crítico da programação de baixa latência provavelmente será um controle muito explícito sobre a memória para melhorar a localidade de referência, bem como apenas a velocidade geral de alocar e desalocar memória. Na verdade, uma memória de pool de alocador personalizado ecoa o mesmo tipo de mentalidade de design que descrevemos. Ele foi projetado para granel ; foi projetado em um nível aproximado. Ele pré-aloca a memória em grandes blocos e agrupa a memória já alocada em pequenos blocos.

A idéia é exatamente a mesma de empurrar coisas caras (alocar um pedaço de memória contra um alocador de uso geral, por exemplo) para um nível cada vez mais grosso. Um conjunto de memórias foi projetado para lidar com memória em massa .

Sistemas de tipo segregam memória

Uma das dificuldades do design granular orientado a objetos em qualquer idioma é que ele geralmente quer introduzir muitos tipos e estruturas de dados definidas pelo usuário. Esses tipos podem querer ser alocados em pequenos pedaços pequenos, se forem alocados dinamicamente.

Um exemplo comum em C ++ seria nos casos em que o polimorfismo é necessário, onde a tentação natural é alocar cada instância de uma subclasse contra um alocador de memória de uso geral.

Isso acaba desmembrando layouts de memória possivelmente contíguos em pequenos bits e pedaços espalhados pelo intervalo de endereços, o que se traduz em mais falhas de página e falhas de cache.

Os campos que exigem a resposta determinística de menor latência, sem gagueira são provavelmente o único lugar onde os pontos de acesso nem sempre se resumem a um único gargalo, onde pequenas ineficiências podem realmente realmente se "acumular" (algo que muitas pessoas imaginam acontecendo incorretamente com um criador de perfil para mantê-los sob controle, mas em campos controlados por latência, pode haver alguns casos raros em que pequenas ineficiências se acumulam). E muitas das razões mais comuns para esse acúmulo podem ser as seguintes: a alocação excessiva de pequenos pedaços de memória em todo o lugar.

Em linguagens como Java, pode ser útil usar mais matrizes de tipos de dados antigos simples quando possível para áreas de gargalo (áreas processadas em loops apertados), como uma matriz de int(mas ainda por trás de uma interface de alto nível volumosa) em vez de, digamos , um ArrayListdos Integerobjetos definidos pelo usuário . Isso evita a segregação de memória que normalmente acompanha o último. No C ++, não precisamos degradar a estrutura tanto se nossos padrões de alocação de memória forem eficientes, pois os tipos definidos pelo usuário podem ser alocados de forma contígua e mesmo no contexto de um contêiner genérico.

Fundindo a memória novamente

Uma solução aqui é buscar um alocador personalizado para tipos de dados homogêneos e, possivelmente, mesmo entre tipos de dados homogêneos. Quando pequenos tipos de dados e estruturas de dados são achatadas em bits e bytes na memória, elas assumem uma natureza homogênea (embora com alguns requisitos variados de alinhamento). Quando não os olhamos de uma mentalidade centrada na memória, o sistema de tipos de linguagens de programação "deseja" dividir / segregar regiões de memória potencialmente contíguas em pequenos pedaços dispersos.

A pilha utiliza esse foco centralizado na memória para evitar isso e potencialmente armazena qualquer combinação mista possível de instâncias do tipo definido pelo usuário. Utilizar mais a pilha é uma ótima idéia, quando possível, pois quase sempre ela fica em uma linha de cache, mas também podemos projetar alocadores de memória que imitam algumas dessas características sem um padrão LIFO, fundindo a memória entre tipos de dados diferentes em tipos de dados contíguos. até mesmo para padrões mais complexos de alocação e desalocação de memória.

O hardware moderno foi projetado para atingir seu pico ao processar blocos contíguos de memória (acessando repetidamente a mesma linha de cache, a mesma página, por exemplo). A palavra-chave existe contiguidade, pois isso só é benéfico se houver dados de interesse ao redor. Portanto, grande parte da chave (mas também dificuldade) do desempenho é reunir novamente pedaços de memória segregados em blocos contíguos que são acessados ​​na íntegra (todos os dados ao redor são relevantes) antes da remoção. O sistema de tipos avançados de tipos especialmente definidos pelo usuário em linguagens de programação pode ser o maior obstáculo aqui, mas sempre podemos procurar e resolver o problema por meio de um alocador personalizado e / ou projetos mais volumosos, quando apropriado.

Feio

"Feio" é difícil de dizer. É uma métrica subjetiva, e alguém que trabalha em um campo muito crítico de desempenho começará a mudar sua idéia de "beleza" para uma que é muito mais orientada a dados e se concentra nas interfaces que processam as coisas em massa.

Perigoso

"Perigoso" pode ser mais fácil. Em geral, o desempenho tende a alcançar um código de nível inferior. A implementação de um alocador de memória, por exemplo, é impossível sem chegar abaixo dos tipos de dados e trabalhar no nível perigoso de bits e bytes brutos. Como resultado, pode ajudar a aumentar o foco em procedimentos de teste cuidadosos nesses subsistemas críticos para o desempenho, dimensionando a profundidade dos testes com o nível de otimizações aplicado.

Beleza

No entanto, tudo isso estaria no nível de detalhes da implementação. Tanto em uma mentalidade veterana em larga escala quanto em crítica ao desempenho, a "beleza" tende a mudar para designs de interface, em vez de detalhes de implementação. Torna-se uma prioridade exponencialmente mais alta buscar interfaces "bonitas", utilizáveis, seguras e eficientes, em vez de implementações devido a falhas de acoplamento e cascata que podem ocorrer diante de uma alteração no design da interface. As implementações podem ser trocadas a qualquer momento. Normalmente, iteramos para o desempenho conforme necessário e conforme indicado pelas medições. A chave do design da interface é modelar em um nível grosso o suficiente para deixar espaço para essas iterações sem interromper o sistema inteiro.

De fato, eu sugeriria que o foco de um veterano no desenvolvimento crítico de desempenho tende a colocar predominantemente um foco predominante em segurança, testes, manutenibilidade, apenas o discípulo da SE em geral, uma vez que uma base de código em larga escala que possui vários desempenhos Os subsistemas críticos (sistemas de partículas, algoritmos de processamento de imagem, processamento de vídeo, feedback de áudio, rastreadores de raios, mecanismos de malha etc.) precisarão prestar muita atenção à engenharia de software para evitar afogamentos em um pesadelo de manutenção. Não é por mera coincidência que muitas vezes os produtos mais surpreendentemente eficientes do mercado também podem ter o menor número de bugs.

TL; DR

De qualquer forma, essa é minha opinião sobre o assunto, variando de prioridades em campos genuinamente críticos para o desempenho, o que pode reduzir a latência e fazer com que pequenas ineficiências se acumulem, e o que realmente constitui "beleza" (quando se olha para as coisas de maneira mais produtiva).


fonte
0

Não deve ser diferente, mas aqui está o que eu faço:

  1. Escreva de forma limpa e sustentável.

  2. Faça o diagnóstico de desempenho e corrija os problemas que ele indicar, não os que você adivinha. Garantido, eles serão diferentes do que você espera.

Você pode fazer essas correções de uma maneira que ainda seja clara e sustentável, mas precisará adicionar comentários para que as pessoas que olham o código saibam por que você fez dessa maneira. Caso contrário, eles desfarão.

Então, existe uma troca? Eu realmente não penso assim.

Mike Dunlavey
fonte
0

Você pode escrever um código feio que é muito rápido e também um código bonito que é tão rápido quanto o seu código feio. O gargalo não estará na beleza / organização / estrutura do seu código, mas nas técnicas que você escolheu. Por exemplo, você está usando soquetes sem bloqueio? Você está usando design de thread único? Você está usando uma fila sem bloqueio para comunicação entre threads? Você está produzindo lixo para o GC? Você está executando alguma operação de E / S de bloqueio no encadeamento crítico? Como você pode ver, isso não tem nada a ver com beleza.

rdalmeida
fonte
0

O que importa para o usuário final?

  • atuação
  • Características / Funcionalidade
  • desenhar

Caso 1: código incorreto otimizado

  • Manutenção difícil
  • Dificilmente legível se como um projeto de código aberto

Caso 2: código bom não otimizado

  • Manutenção fácil
  • Má experiência do usuário

Solução?

Fácil, otimize trechos de código críticos de desempenho

por exemplo:

Um programa que consiste em 5 métodos , 3 deles são para gerenciamento de dados, 1 para leitura de disco e outro para gravação de disco

Esses 3 métodos de gerenciamento de dados usam os dois métodos de E / S e dependem deles

Otimizamos os métodos de E / S.

Motivo: É menos provável que os métodos de E / S sejam alterados, nem afetam o design do aplicativo. No geral, tudo nesse programa depende deles e, portanto, eles parecem críticos para o desempenho, usaríamos qualquer código para otimizá-los. .

Isso significa que obtemos um bom código e design gerenciável do programa, mantendo-o rápido, otimizando certas partes do código

Eu estou pensando..

Eu acho que código ruim dificulta a otimização do polonês e pequenos erros podem torná-lo ainda pior, então um bom código para iniciantes / iniciantes seria melhor se fosse bem escrito esse código feio.

OverCoder
fonte