Por que é difícil conceder eficiência ao usar bibliotecas?

10

Qualquer pequeno processamento de banco de dados pode ser facilmente enfrentado por scripts Python / Perl / ..., que usam bibliotecas e / ou utilitários da própria linguagem. No entanto, quando se trata de desempenho, as pessoas tendem a buscar linguagens C / C ++ / de baixo nível. A possibilidade de adaptar o código às necessidades parece ser o que torna essas linguagens tão atraentes para o BigData - seja sobre gerenciamento de memória, paralelismo, acesso ao disco ou até otimizações de baixo nível (por meio de construções de montagem no nível C / C ++).

É claro que esse conjunto de benefícios não teria um custo: escrever o código e, às vezes, até reinventar a roda , pode ser bastante caro / cansativo. Embora existam muitas bibliotecas disponíveis, as pessoas tendem a escrever o código sozinhas sempre que precisam conceder desempenho. O que desativa as asserções de desempenho do uso de bibliotecas durante o processamento de bancos de dados grandes?

Por exemplo, considere uma empresa que rastreia continuamente páginas da Web e analisa os dados coletados. Para cada janela deslizante, algoritmos diferentes de mineração de dados são executados com os dados extraídos. Por que os desenvolvedores desistiram de usar as bibliotecas / estruturas disponíveis (seja para rastreamento, processamento de texto e mineração de dados)? Usar coisas já implementadas não apenas aliviaria o ônus da codificação de todo o processo, mas também pouparia muito tempo.

Em um único tiro :

  • o que torna a escrita do código uma garantia de desempenho?
  • por que é arriscado confiar em estruturas / bibliotecas quando você deve garantir alto desempenho?
Rubens
fonte
11
Você pode esclarecer a pergunta exata? Talvez algumas respostas possíveis que você tenha em mente também possam ajudar.
Amir Ali Akbari
@AmirAliAkbari SeanOwen postou uma resposta e notei a falta de especificidade na minha pergunta. Adicionei um comentário ao seu post. Por favor, sinta-se à vontade para sugerir melhorias na postagem - planejo excluí-la, caso contrário.
Rubens

Respostas:

4

Tendo feito o jogo de reescrever várias vezes (e ainda o fazendo), minha reação imediata foi a adaptabilidade .

Embora estruturas e bibliotecas possuam um arsenal enorme de rotinas (possivelmente interwináveis) para tarefas padrão, suas propriedades de estrutura geralmente (sempre?) Não permitem atalhos. De fato, a maioria das estruturas possui algum tipo de infraestrutura principal em torno da qual uma camada básica de funcionalidade básica é implementada. Funcionalidades mais específicas fazem uso da camada básica e são colocadas em uma segunda camada ao redor do núcleo.

Agora, com atalhos, quero dizer passar direto de uma rotina da segunda camada para outra rotina da segunda camada sem usar o núcleo. Um exemplo típico (do meu domínio) seria o registro de data e hora: você tem algum tipo de fonte de dados com registro de data e hora. Até agora, o trabalho é simplesmente ler os dados e transmiti-los ao núcleo para que seu outro código possa se deleitar.

Agora, seu setor altera o formato padrão do registro de data e hora por um motivo muito bom (no meu caso, eles passaram da hora unix para a hora do GPS). A menos que sua estrutura seja específica do setor, é muito improvável que eles estejam dispostos a alterar a representação principal do tempo, então você acaba usando uma estrutura que quase faz o que deseja. Toda vez que você acessa seus dados, você deve primeiro convertê-los para o formato de horário da indústria e toda vez que desejar modificá-los, deve convertê-los novamente para o que o núcleo considerar apropriado. Não há como você entregar dados diretamente da fonte para um coletor sem conversão dupla.

É aqui que suas estruturas artesanais brilharão, é apenas uma pequena mudança e você voltará a modelar o mundo real, enquanto todas as outras estruturas (não específicas do setor) agora terão uma desvantagem de desempenho.

Com o tempo, a discrepância entre o mundo real e o modelo aumentará. Com um quadro de off-the-shelf você logo estar voltado para perguntas como: Como posso representar thisem thatou como fazem rotina Xaceitar / produto Y.

Até agora, não se tratava de C / C ++. Mas se, por algum motivo, você não puder alterar a estrutura, ou seja, você precisará tolerar a conversão dupla de dados para ir de uma extremidade à outra, normalmente empregaria algo que minimiza a sobrecarga adicional. No meu caso, é melhor deixar um conversor TAI-> UTC ou UTC-> TAI para C bruto (ou um FPGA). Não há elegância possível, nenhuma estrutura de dados inteligente profunda que torne o problema trivial. É apenas uma declaração chata de switch, e por que não usar uma linguagem cujos compiladores sejam bons em otimizar exatamente isso?

hroptatyr
fonte
11
+1 Pode ser minha culpa por não estar muito claro na minha postagem, para que outros não a tivessem entendido antes. Este é certamente o tipo de resposta que eu estava procurando. Obrigado.
Rubens
7

Não acho que todos procurem C / C ++ quando o desempenho for um problema.

A vantagem de escrever código de baixo nível é usar menos ciclos da CPU ou, às vezes, menos memória. Mas eu observaria que os idiomas de nível superior podem chamar para idiomas de nível inferior, e o fazem, para obter parte desse valor. As linguagens Python e JVM podem fazer isso.

A cientista de dados que usa, por exemplo, o scikit-learn em sua área de trabalho já está chamando rotinas nativas altamente otimizadas para fazer o processamento de números. Não faz sentido escrever um novo código para velocidade.

No contexto de "big data" distribuído, você costuma ter um gargalo na movimentação de dados: transferência de rede e E / S. Código nativo não ajuda. O que ajuda é não escrever o mesmo código para executar mais rapidamente, mas escrever um código mais inteligente.

Linguagens de nível superior permitem implementar algoritmos distribuídos mais sofisticados em um determinado período de tempo do desenvolvedor que o C / C ++. Em escala, o algoritmo mais inteligente com melhor movimentação de dados superará o código nativo idiota.

Também é verdade que o tempo do desenvolvedor, e os erros, custam muito mais do que o novo hardware. Um ano do tempo de um desenvolvedor sênior pode custar US $ 200 mil totalmente carregado; mais de um ano que também aluga centenas de servidores no valor de tempo de computação. Na maioria dos casos, talvez não faça sentido se preocupar em otimizar o lançamento de mais hardware.

Não entendo o acompanhamento sobre "conceder" e "desativar" e "afirmar"?

Sean Owen
fonte
Desculpe pelo mal entendido. Minha intenção era trazer respostas sobre a importância de ter controle sobre um aplicativo e como esse controle é afrouxado pelas bibliotecas. É claro que você pode assumir coisas sobre eles (as pessoas normalmente não reescrevem pthreads), mas se os dados mudarem (carga, taxa de transferência, ...), talvez você precise acessar a fonte lib para garantir o desempenho. E sim, não é necessariamente C / C ++ - embora eles geralmente sejam os idiomas escolhidos para hpc. Posso excluir minha pergunta ou gostaria de alterá-la para algo mais específico? Aceito sugestões para melhorá-lo.
Rubens
11
Não, é uma boa pergunta, você pode refletir seus comentários aqui em edições da pergunta, se quiser.
Sean Owen
Por favor, verifique se a pergunta faz sentido agora. Adicionei um pequeno estojo para torná-lo mais direto. Caso queira adicionar alguma consideração à pergunta, fique à vontade para editá-la.
Rubens
4

Como sabemos, no mundo digital, existem muitas maneiras de fazer o mesmo trabalho / obter os resultados esperados.

E as responsabilidades / riscos resultantes do código estão nos ombros dos desenvolvedores.

Isso é pequeno, mas acho que é um exemplo muito útil do mundo .NET.

Muitos desenvolvedores .NET usam o BinaryReader - BinaryWriter interno em sua serialização de dados para obter desempenho / obter controle sobre o processo.

Este é o código-fonte CSharp da classe BinaryWriter incorporada do FrameWork ', um dos métodos de gravação sobrecarregados:

// Writes a boolean to this stream. A single byte is written to the stream
// with the value 0 representing false or the value 1 representing true.
// 
public virtual void Write(bool value) 
{
     //_buffer is a byte array which declared in ctor / init codes of the class
    _buffer = ((byte) (value? 1:0));

    //OutStream is the stream instance which BinaryWriter Writes the value(s) into it.
    OutStream.WriteByte(_buffer[0]);
}

Como você vê, esse método pode ser escrito sem a atribuição extra à variável _buffer:

public virtual void Write(bool value) 
{
    OutStream.WriteByte((byte) (value ? 1 : 0));
}

Sem atribuir, poderíamos ganhar alguns milissegundos .. Esses poucos milissegundos podem aceitar como "quase nada", mas e se houver milhares de gravações (ou seja, em um processo de servidor)?

Vamos supor que "poucas" sejam 2 (milissegundos) e as instâncias de vários milhares são apenas 2.000. Isso significa 4 segundos a mais de tempo de processamento .. 4 segundos depois retornando ..

Se continuarmos sujeitos do .NET e se você puder verificar os códigos-fonte da BCL - Biblioteca de classes .NET Base - do MSDN, poderá ver muito desempenho perder por parte do desenvolvedor.

Qualquer um dos pontos da fonte BCL É normal que você veja o desenvolvedor decidir usar loops while () ou foreach () que podem implementar um loop for () mais rápido em seu código.

Esses pequenos ganhos nos dão o desempenho total ..

E se retornarmos ao método BinaryWriter.Write ()

Na verdade, atribuir extra a uma implementação _buffer não é uma falha do desenvolvedor. Isso é exatamente decidir "ficar em segurança"!

Suponha que decidimos não usar _buffer e decidimos implementar o segundo método. Se tentarmos enviar milhares de bytes por uma conexão (por exemplo, carregar / baixar dados BLOB ou CLOB) com o segundo método, ele pode falhar normalmente porque de conexão perdida .. porque tentamos enviar todos os dados sem nenhuma verificação e mecanismo de controle. Quando a conexão é perdida, o servidor e o cliente nunca sabem que os dados enviados foram concluídos ou não.

Se o desenvolvedor decidir "permanecer em segurança", normalmente significa que os custos de desempenho dependem dos mecanismos "permanecem em segurança" implementados.

Mas se o desenvolvedor decidir "se arriscar, obter desempenho", isso também não é uma falha ... Até que haja algumas discussões sobre codificação "arriscada".

E como uma pequena observação: os desenvolvedores de bibliotecas comerciais sempre tentam permanecer em segurança, porque não sabem onde seu código será usado.

sihirbazzz
fonte
4

Vindo da perspectiva dos programadores, as estruturas raramente visam o desempenho como a maior prioridade. Se sua biblioteca for amplamente aproveitada, as coisas que as pessoas provavelmente mais valorizam são a facilidade de uso, a flexibilidade e a confiabilidade.

O desempenho é geralmente avaliado em bibliotecas competitivas secundárias. "A biblioteca X é melhor porque é mais rápida." Mesmo assim, com muita frequência essas bibliotecas trocam a solução mais ideal por uma que possa ser amplamente aproveitada.

Ao usar qualquer estrutura, você está inerentemente arriscando a existência de uma solução mais rápida. Eu poderia chegar ao ponto de dizer que uma solução mais rápida quase sempre existe.

Escrever algo você mesmo não é uma garantia de desempenho, mas se você souber o que está fazendo e tiver um conjunto de requisitos bastante limitado, isso poderá ajudar.

Um exemplo pode ser a análise JSON. Existem centenas de bibliotecas por aí para uma variedade de linguagens que transformarão o JSON em um objeto referenciável e vice-versa. Conheço uma implementação que faz tudo isso nos registros da CPU. É mensurável mais rápido que todos os outros analisadores, mas também é muito limitado e essa limitação varia de acordo com a CPU com a qual você está trabalhando.

A tarefa de construir um analisador JSON específico do ambiente de alto desempenho é uma boa idéia? Eu usaria uma biblioteca respeitada 99 vezes em 100. Nesse caso separado, alguns ciclos extras de CPU multiplicados por um milhão de iterações fariam o tempo de desenvolvimento valer a pena.

Steve Kallestad
fonte