Desempenho em C ++ vs. Java / C #


119

Meu entendimento é que o C / C ++ produz código nativo para executar em uma arquitetura de máquina específica. Por outro lado, linguagens como Java e C # são executadas em cima de uma máquina virtual que abstrai a arquitetura nativa. Logicamente, parece impossível para Java ou C # corresponder à velocidade do C ++ devido a esta etapa intermediária, no entanto, me disseram que os compiladores mais recentes ("hot spot") podem atingir essa velocidade ou até excedê-la.

Talvez seja mais uma questão de compilador do que de idioma, mas alguém pode explicar em inglês simples como é possível que um desses idiomas de máquina virtual tenha um desempenho melhor que o idioma nativo?


Java e C # podem otimizar com base em como o aplicativo é realmente executado usando o código, uma vez que está disponível no tempo de execução. por exemplo, ele pode incorporar código em uma biblioteca compartilhada que pode realmente mudar enquanto o programa está sendo executado e ainda estar correto.
Peter Lawrey #

Algumas medidas reais para verificar antes de ler muita teoria muito esquisita nestas respostas: shootout.alioth.debian.org/u32/…
Justicle

Respostas:


178

Geralmente, C # e Java podem ser tão rápidos ou mais rápidos porque o compilador JIT - um compilador que compila sua IL na primeira vez que é executado - pode fazer otimizações que um programa compilado em C ++ não pode porque pode consultar a máquina. Pode determinar se a máquina é Intel ou AMD; Pentium 4, Core Solo ou Core Duo; ou se suporta SSE4, etc.

Um programa C ++ precisa ser compilado previamente, geralmente com otimizações mistas, para que ele decida bem em todas as máquinas, mas não seja otimizado tanto quanto poderia ser para uma única configuração (por exemplo, processador, conjunto de instruções, outro hardware).

Além disso, certos recursos de linguagem permitem que o compilador em C # e Java faça suposições sobre seu código, o que permite otimizar determinadas partes que não são seguras para o compilador C / C ++. Quando você tem acesso aos ponteiros, há muitas otimizações que simplesmente não são seguras.

Além disso, Java e C # podem fazer alocações de heap de maneira mais eficiente que C ++, porque a camada de abstração entre o coletor de lixo e seu código permite que ele faça toda a compactação de heap de uma só vez (uma operação bastante cara).

Agora não posso falar sobre Java neste próximo ponto, mas sei que o C #, por exemplo, removerá métodos e chamadas de métodos quando souber que o corpo do método está vazio. E ele usará esse tipo de lógica em todo o seu código.

Portanto, como você pode ver, existem várias razões pelas quais determinadas implementações em C # ou Java serão mais rápidas.

Agora, tudo isso dito, otimizações específicas podem ser feitas em C ++ que explodirão qualquer coisa que você possa fazer com C #, especialmente na área de gráficos e sempre que estiver perto do hardware. Ponteiros fazem maravilhas aqui.

Então, dependendo do que você está escrevendo, eu iria com um ou outro. Mas se você estiver escrevendo algo que não depende de hardware (driver, videogame etc.), não me preocuparia com o desempenho do C # (novamente não posso falar sobre Java). Vai dar certo.

Do lado do Java, o @Swati aponta um bom artigo:

https://www.ibm.com/developerworks/library/j-jtp09275


Seu raciocínio é falso - os programas C ++ são construídos para a arquitetura de destino e não precisam mudar em tempo de execução.
precisa saber é o seguinte

3
@Justicle O melhor que seu compilador c ++ oferecerá para diferentes arquiteturas é geralmente x86, x64, ARM e outros enfeites. Agora você pode dizer a ele para usar recursos específicos (por exemplo, SSE2) e, se tiver sorte, até gerará algum código de backup se esse recurso não estiver disponível, mas é o mais refinado possível. Certamente nenhuma especialização, dependendo do tamanho do cache e outros enfeites.
Voo

4
Veja shootout.alioth.debian.org/u32/… para exemplos dessa teoria que não estão acontecendo.
Justicle

1
Para ser sincero, esta é uma das piores respostas. É tão infundado que eu poderia invertê-lo. Muita generalização, muita desconhecimento (otimizar funções vazias é realmente apenas a ponta do iceberg). Um compilador C ++ de luxo possui: Time. Outro luxo: nenhuma verificação é imposta. Mas encontre mais em stackoverflow.com/questions/145110/c-performance-vs-java-c/… .
Sebastian Mach

1
@OrionAdrian ok, estamos em um círculo completo agora ... Veja shootout.alioth.debian.org/u32/… para exemplos dessa teoria que não estão acontecendo. Em outras palavras, mostre-nos que sua teoria pode ser comprovada correta antes de fazer declarações especulativas vagas.
Justicle

197

JIT vs. Compilador Estático

Como já foi dito nas postagens anteriores, o JIT pode compilar IL / bytecode no código nativo em tempo de execução. O custo disso foi mencionado, mas não a sua conclusão:

O JIT tem um grande problema: não é possível compilar tudo: a compilação do JIT leva tempo, portanto o JIT compilará apenas algumas partes do código, enquanto um compilador estático produzirá um binário nativo completo: Para alguns tipos de programas, o estático O compilador simplesmente superará facilmente o JIT.

É claro que C # (ou Java ou VB) geralmente é mais rápido para produzir uma solução viável e robusta do que o C ++ (apenas porque o C ++ possui semântica complexa e a biblioteca padrão do C ++, embora interessante e poderosa, é bastante ruim quando comparada à versão completa escopo da biblioteca padrão do .NET ou Java); portanto, normalmente, a diferença entre C ++ e .NET ou Java JIT não será visível para a maioria dos usuários e, para os binários críticos, bem, você ainda pode chamar o processamento C ++ de C # ou Java (mesmo que esse tipo de chamada nativa possa ser bastante caro por si só) ...

Metaprogramação em C ++

Observe que geralmente você está comparando o código de tempo de execução C ++ com seu equivalente em C # ou Java. Mas o C ++ tem um recurso que pode superar o Java / C # imediatamente, que é a metaprogramação de modelos: O processamento do código será feito no tempo de compilação (aumentando assim o tempo de compilação), resultando em zero (ou quase zero) tempo de execução.

Eu ainda tenho um efeito na vida real sobre isso (eu joguei apenas com conceitos, mas até então, a diferença era segundos de execução para JIT e zero para C ++), mas vale a pena mencionar, juntamente com a metaprogramação do modelo de fato não é trivial...

Edit 2011-06-10: No C ++, a reprodução de tipos é feita em tempo de compilação, o que significa produzir código genérico que chama código não genérico (por exemplo, um analisador genérico de string para o tipo T, chamando a API da biblioteca padrão para os tipos T que reconhece, e tornar o analisador facilmente extensível pelo usuário) é muito fácil e muito eficiente, enquanto o equivalente em Java ou C # é doloroso na melhor das hipóteses de escrever e sempre será mais lento e resolvido em tempo de execução, mesmo quando os tipos são conhecidos em tempo de compilação, ou seja, sua única esperança é que o JIT incline tudo.

...

Edit 2011-09-20: A equipe por trás do Blitz ++ ( Homepage , Wikipedia ) seguiu esse caminho e, aparentemente, seu objetivo é alcançar o desempenho do FORTRAN em cálculos científicos, movendo o máximo possível da execução do tempo de execução para o tempo de compilação, via metaprogramação de modelos C ++ . Assim, o " eu ainda assim ver um efeito da vida real neste " parte eu escrevi acima, aparentemente, não existe na vida real.

Uso de memória C ++ nativa

O C ++ possui um uso de memória diferente de Java / C # e, portanto, possui vantagens / falhas diferentes.

Independentemente da otimização do JIT, nada será rápido como o acesso direto do ponteiro à memória (vamos ignorar por um momento os caches do processador, etc.). Portanto, se você tiver dados contíguos na memória, acessá-los através de ponteiros C ++ (isto é, ponteiros C ... Vamos dar o devido ao Caesar) passará vezes mais rápido do que em Java / C #. E o C ++ possui RAII, o que facilita muito o processamento do que em C # ou mesmo em Java. C ++ não precisa usingescopo a existência de seus objetos. E o C ++ não possui uma finallycláusula. Isso não é um erro.

:-)

E, apesar das estruturas primitivas do C #, os objetos C ++ "na pilha" não custarão nada na alocação e destruição e não precisarão de GC para trabalhar em um encadeamento independente para fazer a limpeza.

Quanto à fragmentação da memória, os alocadores de memória em 2008 não são os antigos alocadores de memória de 1980 que geralmente são comparados a uma alocação GC: C ++ não pode ser movida na memória, é verdade, mas, como em um sistema de arquivos Linux: quem precisa de disco rígido desfragmentar quando a fragmentação não acontece? O uso do alocador correto para a tarefa correta deve fazer parte do kit de ferramentas do desenvolvedor C ++. Agora, escrever alocadores não é fácil e, em seguida, a maioria de nós tem coisas melhores para fazer e, na maior parte do tempo, RAII ou GC é mais que suficiente.

Editar 2011-10-04: Para exemplos sobre alocadores eficientes: Nas plataformas Windows, desde o Vista, o Heap de fragmentação baixa é ativado por padrão. Para versões anteriores, o LFH pode ser ativado chamando a função WinAPI HeapSetInformation ). Em outros sistemas operacionais, alocadores alternativos são fornecidos (consultehttps://secure.wikimedia.org/wikipedia/en/wiki/Malloc para obter uma lista)

Agora, o modelo de memória está se tornando um pouco mais complicado com o aumento da tecnologia multicore e multithreading. Nesse campo, acho que o .NET tem a vantagem, e o Java, disseram-me, manteve o nível superior. É fácil para alguns hackers "simples" elogiarem seu código "próximo à máquina". Mas agora, é muito mais difícil produzir melhor montagem manualmente do que deixar o compilador trabalhar. Para C ++, o compilador geralmente se tornou melhor que o hacker desde uma década. Para C # e Java, isso é ainda mais fácil.

Ainda assim, o novo C ++ 0x padrão imporá um modelo de memória simples aos compiladores C ++, que padronizará (e, portanto, simplificará) o código efetivo de multiprocessamento / paralelo / encadeamento em C ++, e tornará as otimizações mais fáceis e seguras para os compiladores. Mas então, veremos em alguns anos se suas promessas são cumpridas.

C ++ / CLI vs. C # / VB.NET

Nota: Nesta seção, estou falando sobre C ++ / CLI, ou seja, o C ++ hospedado pelo .NET, não o C ++ nativo.

Na semana passada, tive um treinamento sobre otimização do .NET e descobri que o compilador estático é muito importante de qualquer maneira. Tão importante quanto o JIT.

O mesmo código compilado em C ++ / CLI (ou seu ancestral, C ++ gerenciado) pode ser mais rápido que o mesmo código produzido em C # (ou VB.NET, cujo compilador produz a mesma IL que C #).

Como o compilador estático C ++ era muito melhor para produzir código já otimizado do que os C #.

Por exemplo, a função embutida no .NET é limitada a funções cujo código de bytes é menor ou igual a 32 bytes de comprimento. Portanto, algum código em C # produzirá um acessador de 40 bytes, que nunca será incorporado pelo JIT. O mesmo código em C ++ / CLI produzirá um acessador de 20 bytes, que será incorporado pelo JIT.

Outro exemplo são as variáveis ​​temporárias, que são simplesmente compiladas pelo compilador C ++ enquanto ainda são mencionadas na IL produzida pelo compilador C #. A otimização da compilação estática em C ++ resultará em menos código, autorizando uma otimização JIT mais agressiva novamente.

A razão para isso foi especulada como o fato de o compilador C ++ / CLI lucrar com as vastas técnicas de otimização do compilador nativo C ++.

Conclusão

Eu amo C ++.

Mas, na minha opinião, C # ou Java são uma aposta melhor. Não porque eles são mais rápidos que o C ++, mas porque quando você soma suas qualidades, eles acabam sendo mais produtivos, precisando de menos treinamento e tendo bibliotecas padrão mais completas que o C ++. E, como na maioria dos programas, suas diferenças de velocidade (de uma maneira ou de outra) serão desprezíveis ...

Editar (06/06/2011)

Minha experiência em C # / .NET

Eu tenho agora 5 meses de codificação C # profissional quase exclusiva (que adiciona ao meu currículo já cheio de C ++ e Java e um toque de C ++ / CLI).

Eu joguei com WinForms (Ahem ...) e WCF (legal!) E WPF (legal !!!! Tanto por XAML quanto por C # bruto. O WPF é tão fácil que acredito que o Swing simplesmente não se compara a ele) e C # 4.0.

A conclusão é que, embora seja mais fácil / rápido produzir um código que funcione em C # / Java do que em C ++, é muito mais difícil produzir um código forte, seguro e robusto em C # (e ainda mais difícil em Java) do que em C ++. Os motivos não faltam, mas podem ser resumidos por:

  1. Os genéricos não são tão poderosos quanto os modelos ( tente escrever um método Parse genérico eficiente (de string para T) ou um equivalente eficiente de boost :: lexical_cast em C # para entender o problema )
  2. O RAII permanece incomparável (o GC ainda pode vazar (sim, eu tive que lidar com esse problema) e só lidará com a memória. Mesmo os C # usingnão são tão fáceis e poderosos porque é difícil escrever implementações corretas do Dispose .
  3. C # readonlye Java finalnão são tão úteis quanto os de C ++const ( não há como você expor dados complexos somente de leitura (uma Árvore de Nós, por exemplo) em C # sem um tremendo trabalho, embora seja um recurso interno do C ++. Dados imutáveis ​​são uma solução interessante , mas nem tudo pode ser imutável, portanto, de longe, não é o suficiente ).

Portanto, o C # permanece uma linguagem agradável, desde que você queira algo que funcione, mas uma linguagem frustrante no momento em que você deseja algo que sempre e com segurança funcione.

O Java é ainda mais frustrante, pois tem os mesmos problemas que o C # e mais: como não possui a usingpalavra-chave C # , um colega muito experiente gastou muito tempo para garantir que seus recursos fossem liberados corretamente, enquanto o equivalente em C ++ teria foi fácil (usando destruidores e ponteiros inteligentes).

Então, acho que o ganho de produtividade do C # / Java é visível para a maioria dos códigos ... até o dia em que você precisar que o código seja o mais perfeito possível. Nesse dia, você conhecerá a dor. (você não vai acreditar no que é solicitado em nossos aplicativos de servidor e GUI ...).

Sobre Java e C ++ do lado do servidor

Eu mantive contato com as equipes de servidores (trabalhei 2 anos entre elas, antes de retornar à equipe da GUI), do outro lado do prédio, e aprendi algo interessante.

Nos últimos anos, a tendência era ter os aplicativos de servidor Java destinados a substituir os aplicativos antigos de servidor C ++, pois o Java possui muitas estruturas / ferramentas e é fácil de manter, implantar etc. etc.

... Até o problema da baixa latência elevar sua cabeça feia nos últimos meses. Então, os aplicativos de servidor Java, independentemente da otimização tentada por nossa equipe qualificada de Java, perderam de forma simples e limpa a corrida contra o antigo servidor C ++ não otimizado.

Atualmente, a decisão é manter os servidores Java para uso comum, onde o desempenho ainda é importante, não está preocupado com o destino de baixa latência e otimizar agressivamente os aplicativos do servidor C ++, que são mais rápidos, para necessidades de baixa latência e ultra-baixa latência.

Conclusão

Nada é tão simples quanto o esperado.

Java e ainda mais C # são linguagens legais, com extensas bibliotecas e estruturas padrão, nas quais você pode codificar rapidamente e ter resultados muito em breve.

Mas quando você precisa de força bruta, otimizações poderosas e sistemáticas, suporte forte ao compilador, recursos poderosos de linguagem e segurança absoluta, Java e C # dificultam a conquista dos últimos percentuais de qualidade ausentes mas críticos, que você precisa manter-se acima da concorrência.

É como se você precisasse de menos tempo e menos desenvolvedores experientes em C # / Java do que em C ++ para produzir código de qualidade média, mas, por outro lado, no momento em que você precisava de um código de qualidade excelente a perfeito, subitamente ficou mais fácil e rápido obter os resultados certo em C ++.

Obviamente, essa é minha própria percepção, talvez limitada a nossas necessidades específicas.

Mas, ainda assim, é o que acontece hoje, tanto nas equipes da GUI quanto nas equipes do lado do servidor.

Obviamente, atualizarei este post se algo novo acontecer.

Editar (22/06/2011)

"Descobrimos que, em relação ao desempenho, o C ++ vence por uma grande margem. No entanto, também exigiu os mais extensos esforços de ajuste, muitos dos quais foram feitos em um nível de sofisticação que não estaria disponível para o programador médio.

[...] A versão Java foi provavelmente a mais simples de implementar, mas a mais difícil de analisar em termos de desempenho. Especificamente, os efeitos em torno da coleta de lixo foram complicados e muito difíceis de ajustar ".

Fontes:

Editar (20/09/2011)

"A palavra certa no Facebook é que ' código C ++ razoavelmente escrito é rápido ' , o que ressalta o enorme esforço gasto na otimização de código PHP e Java. Paradoxalmente, o código C ++ é mais difícil de escrever do que em outras linguagens, mas o código eficiente é um muito mais fácil [escrever em C ++ do que em outras línguas]. "

- Herb Sutter em // build / , citando Andrei Alexandrescu

Fontes:


8
Você edita após 5 meses de C # descreve exatamente minha própria experiência (modelos melhor, const melhor, RAII). +1. Esses três continuam sendo meus recursos pessoais para C ++ (ou D, para os quais ainda não tinha tempo).
Sebastian Mach

"O processamento do código será feito no momento da compilação". Portanto, a metaprogramação de modelos apenas funciona no programa está disponível em tempo de compilação, o que geralmente não é o caso, por exemplo, é impossível escrever uma biblioteca de expressões regulares com desempenho competitivo em vanilla C ++ porque é incapaz de gerar código de tempo de execução (um aspecto importante do metaprogramação).
JD

"brincar com tipos é feito em tempo de compilação ... o equivalente em Java ou C # é doloroso na melhor das hipóteses, e sempre será mais lento e resolvido em tempo de execução, mesmo quando os tipos são conhecidos em tempo de compilação". Em C #, isso vale apenas para tipos de referência e não para tipos de valor.
JD

1
"Não importa a otimização do JIT, nada funcionará tão rápido quanto o acesso direto do ponteiro à memória ... se você tiver dados contíguos na memória, acessá-los através de ponteiros C ++ (ou seja, ponteiros C ... Vamos dar o devido devido a César) mais rápido que em Java / C # ". As pessoas observaram o Java batendo o C ++ no teste SOR a partir do benchmark SciMark2 precisamente porque os ponteiros impedem otimizações relacionadas a aliases. blogs.oracle.com/dagastine/entry/sun_java_is_faster_than
JD

Também é importante notar que o .NET digita a especialização de genéricos nas bibliotecas vinculadas dinamicamente após a vinculação, enquanto o C ++ não pode porque os modelos devem ser resolvidos antes da vinculação. E, obviamente, a grande vantagem que os genéricos têm sobre os modelos são as mensagens de erro compreensíveis.
JD

48

Sempre que falo de desempenho gerenciado versus não gerenciado, gosto de apontar para a série Rico (e Raymond) comparando versões C ++ e C # de um dicionário chinês / inglês. Esta pesquisa no Google permitirá que você leia por si mesmo, mas eu gosto do resumo de Rico.

Então, eu tenho vergonha da minha derrota esmagadora? Dificilmente. O código gerenciado obteve um resultado muito bom para praticamente nenhum esforço. Para derrotar o Raymond gerenciado, teve que:

  • Escreva seu próprio arquivo I / O stuff
  • Escreva sua própria classe de string
  • Escreva seu próprio alocador
  • Escreva seu próprio mapeamento internacional

É claro que ele usou bibliotecas de nível inferior disponíveis para fazer isso, mas ainda há muito trabalho. Você pode ligar para o que resta de um programa STL? Acho que não, acho que ele manteve a classe std :: vector que, em última análise, nunca foi um problema e manteve a função find. Praticamente tudo o resto se foi.

Então, sim, você pode definitivamente vencer o CLR. Raymond pode tornar seu programa ainda mais rápido, eu acho.

Curiosamente, o tempo para analisar o arquivo, conforme relatado pelos cronômetros internos de ambos os programas, é aproximadamente o mesmo - 30ms para cada um. A diferença está na sobrecarga.

Para mim, a conclusão é que foram necessárias seis revisões para que a versão não gerenciada superasse a versão gerenciada que era uma porta simples do código não gerenciado original. Se você precisar de todo o último desempenho (e tiver tempo e experiência para obtê-lo), terá que ficar sem gerenciamento, mas, para mim, aproveitarei a vantagem da ordem de magnitude que tenho nas primeiras versões dos 33 % Ganho se tentar 6 vezes.


3
ligação está morto, encontrado artigo mencionado aqui: blogs.msdn.com/b/ricom/archive/2005/05/10/416151.aspx
gjvdkamp

Primeiro de tudo, se olharmos para o código de Raymond Chen, ele claramente não entende muito bem de C ++ ou estruturas de dados. Seu código quase chega diretamente ao código C de baixo nível, mesmo nos casos em que o código C não possui benefícios de desempenho (parece apenas uma espécie de desconfiança e talvez uma falta de conhecimento de como usar os criadores de perfil). Ele também falhou em entender a maneira mais algorítmica de implementar um dicionário (ele usou std :: find pelo amor de Deus). Se há algo de bom sobre Java, Python, C #, etc. - todos eles fornecem dicionários muito eficientes ...
stinky472

Tentativas ou mesmo std :: map se sairiam muito mais favoráveis ​​em relação ao C ++ ou mesmo a uma tabela de hash. Por fim, um dicionário é exatamente o tipo de programa que mais se beneficia das bibliotecas e estruturas de alto nível. Ele não demonstra diferenças na linguagem tanto quanto nas bibliotecas envolvidas (das quais, eu diria com satisfação que o C # é muito mais completo e fornece muito mais ferramentas adequadas para a tarefa). Mostre um programa que manipula grandes blocos de memória em comparação, como um código matricial / vetorial em larga escala. Isso vai resolver isso muito rapidamente, mesmo se, como neste caso, os programadores não sei o que ...
stinky472

26

A compilação para otimizações específicas da CPU geralmente é superestimada. Basta pegar um programa em C ++ e compilar com otimização para pentium PRO e executar em um pentium 4. Em seguida, recompilar com optimize para pentium 4. Passei longas tardes fazendo isso com vários programas. Resultados gerais ?? Geralmente, menos de 2-3% de aumento de desempenho. Portanto, as vantagens teóricas do JIT são quase nenhuma. A maioria das diferenças de desempenho só pode ser observada ao usar os recursos escalares de processamento de dados, algo que eventualmente precisará de ajustes finos manuais para obter o máximo desempenho de qualquer maneira. As otimizações desse tipo são lentas e dispendiosas, o que as torna, às vezes, inadequadas para o JIT.

No mundo real e no aplicativo real, o C ++ ainda é geralmente mais rápido que o java, principalmente devido ao menor espaço de memória que resulta em melhor desempenho do cache.

Mas, para usar todos os recursos do C ++, o desenvolvedor deve trabalhar duro. Você pode obter resultados superiores, mas deve usar seu cérebro para isso. C ++ é uma linguagem que decidiu apresentar a você mais ferramentas, cobrando o preço que você deve aprender para poder usar bem a linguagem.


4
Não é tanto o que você está compilando para otimização da CPU, mas você está compilando para otimização do caminho de tempo de execução. Se você achar que um método é frequentemente chamado com um parâmetro específico, você pode pré-compilar essa rotina com esse parâmetro como uma constante que pode (no caso de um booleano que controla o fluxo) fatorar pedaços de trabalho gigantescos. C ++ não pode chegar perto de fazer esse tipo de otimização.
Bill K

1
Então, como os JITs fazem na recompilação de rotinas para aproveitar os caminhos de execução observados, e quanta diferença isso faz?
David Thornley

2
@ Bill Posso estar misturando duas coisas ... mas a previsão de ramificação não é feita em tempo de execução no pipeline de instruções para atingir objetivos semelhantes, independentemente do idioma?
Hardy,

@ Hardy sim, a CPU pode fazer predição de ramificação independentemente do idioma, mas não pode fatorar um loop inteiro observando que o loop não tem efeito em nada. Ele também não observará que mult (0) está conectado para retornar 0 e apenas substitui a chamada de método inteira por if (param == 0) result = 0; e evite toda a chamada de função / método. C poderia fazer essas coisas se o compilador tivesse uma visão abrangente do que estava acontecendo, mas geralmente não possui informações suficientes no tempo de compilação.
Bill K

21

O JIT (Compilação Just In Time) pode ser incrivelmente rápido, porque otimiza para a plataforma de destino.

Isso significa que ele pode tirar proveito de qualquer truque do compilador que sua CPU possa suportar, independentemente da CPU em que o desenvolvedor escreveu o código.

O conceito básico do .NET JIT funciona assim (bastante simplificado):

Chamando um método pela primeira vez:

  • O código do seu programa chama um método Foo ()
  • O CLR examina o tipo que implementa Foo () e obtém os metadados associados a ele
  • A partir dos metadados, o CLR sabe em qual endereço de memória o IL (código de byte intermediário) está armazenado.
  • O CLR aloca um bloco de memória e chama o JIT.
  • O JIT compila o IL no código nativo, coloca-o na memória alocada e altera o ponteiro da função nos metadados do tipo Foo () para apontar para esse código nativo.
  • O código nativo é executado.

Chamando um método pela segunda vez:

  • O código do seu programa chama um método Foo ()
  • O CLR examina o tipo que implementa Foo () e localiza o ponteiro da função nos metadados.
  • O código nativo neste local de memória é executado.

Como você pode ver, na segunda vez, é praticamente o mesmo processo que o C ++, exceto com a vantagem de otimizações em tempo real.

Dito isto, ainda existem outros problemas gerais que diminuem a velocidade de um idioma gerenciado, mas o JIT ajuda muito.


Aliás, Jonathan, acho que alguém ainda está votando mal nas suas coisas. Quando votei em você, você teve um -1 neste post.
Brian R. Bondy

12

Eu gosto da resposta de Orion Adrian , mas há outro aspecto nela.

A mesma pergunta foi feita décadas atrás sobre linguagem assembly versus linguagens "humanas" como FORTRAN. E parte da resposta é semelhante.

Sim, um programa C ++ é capaz de ser mais rápido que o C # em qualquer algoritmo (não trivial?), Mas o programa em C # geralmente é tão rápido ou mais rápido que uma implementação "ingênua" em C ++ e uma versão otimizada em C ++ levará mais tempo para se desenvolver e ainda poderá superar a versão C # por uma margem muito pequena. Então, vale mesmo a pena?

Você terá que responder a essa pergunta individualmente.

Dito isso, sou fã de C ++ há muito tempo e acho que é uma linguagem incrivelmente expressiva e poderosa - às vezes subestimada. Mas em muitos problemas da "vida real" (para mim, pessoalmente, isso significa "do tipo que sou pago para resolver"), o C # fará o trabalho mais cedo e com mais segurança.

A maior penalidade que você paga? Muitos programas .NET e Java são porcos de memória. Vi aplicativos .NET e Java ocuparem "centenas" de megabytes de memória, quando programas em C ++ de complexidade semelhante mal arranham as "dezenas" de MBs.


7

Não sei com que frequência você descobrirá que o código Java será executado mais rápido que o C ++, mesmo com o Hotspot, mas explicarei como isso pode acontecer.

Pense no código Java compilado como linguagem de máquina interpretada para a JVM. Quando o processador Hotspot percebe que certas partes do código compilado serão usadas várias vezes, ele realiza uma otimização no código da máquina. Como o Assembly de ajuste manual é quase sempre mais rápido que o código compilado em C ++, não há problema em descobrir que o código de máquina ajustado programaticamente não será tão ruim.

Portanto, para código altamente repetitivo, pude ver onde seria possível para a HV Hotspot JVM executar o Java mais rápido que o C ++ ... até que a coleta de lixo entre em jogo. :)


Você poderia expandir a afirmação Since hand-tuning Assembly is almost always faster than C++ compiled code? O que você quer dizer com "assembly de ajuste manual" e "código compilado em C ++"?
22411 paercebal

Bem, é baseado na idéia de que o otimizador de um compilador segue regras, e os codificadores não. Portanto, sempre haverá um código que o otimizador acha que não pode otimizar perfeitamente, enquanto um humano pode, olhando para uma imagem maior ou sabendo mais sobre o que o código realmente está fazendo. Acrescentarei que este é um comentário de 3 anos e sei mais sobre o HotSpot do que costumava, e posso ver facilmente a otimização dinâmica como uma maneira MUITO legal de fazer o código rodar mais rápido.
billjamesdev

1. As otimizações do Hotspot ou de qualquer outro JIT ainda são otimizações do compilador. O JIT tem a vantagem sobre um compilador estático de poder alinhar alguns resultados (código frequentemente chamado), ou até mesmo fazer otimizações com base no processador em execução, mas ainda é uma otimização do compilador. . . 2. Acho que você está falando sobre otimização de algoritmos, não "ajuste fino de montagem". "o ajuste fino da montagem manual por um codificador humano" falhou em produzir melhores resultados do que as otimizações do compilador desde mais de uma década. Na verdade, um jogo humano com a montagem normalmente parafuso fora qualquer otimização ...
paercebal

Ok, entendi que estou usando a terminologia errada, "otimização do compilador" em vez de "otimização estática". Eu apontaria que, pelo menos na indústria de jogos, tão recentemente quanto no PS2 ainda estávamos usando o código de montagem manual em alguns lugares para "otimizar" os chips específicos que sabíamos que estavam no console; Os compiladores cruzados para esses novos chips ainda não são tão sofisticados quanto os das arquiteturas x86. De volta para a pergunta original acima: o JIT tem a vantagem de ser capaz de medir antes da optimização, o que é uma coisa boa (TM)
billjamesdev

Observe que a maioria dos GCs de produção também usa assembler escrito à mão porque o C / C ++ não o corta.
JD5

6

Geralmente, o algoritmo do seu programa será muito mais importante para a velocidade do seu aplicativo do que o idioma . Você pode implementar um algoritmo ruim em qualquer idioma, incluindo C ++. Com isso em mente, você geralmente poderá escrever o código das execuções mais rapidamente em uma linguagem que ajuda a implementar um algoritmo mais eficiente.

As linguagens de nível superior se saem muito bem, fornecendo acesso mais fácil a muitas estruturas de dados pré-criadas eficientes e práticas encorajadoras que ajudarão a evitar código ineficiente. É claro que, às vezes, eles também podem facilitar a criação de um monte de códigos muito lentos, assim você ainda precisa conhecer sua plataforma.

Além disso, o C ++ está atualizando os recursos "novos" (observe as aspas), como contêineres STL, ponteiros automáticos etc. - veja a biblioteca de reforço, por exemplo. E você pode ocasionalmente achar que a maneira mais rápida de realizar alguma tarefa requer uma técnica como a aritmética de ponteiros, proibida em uma linguagem de nível superior - embora elas normalmente permitam que você chame uma biblioteca escrita em uma linguagem que possa implementá-la conforme desejado .

O principal é saber o idioma que você está usando, a API associada, o que pode fazer e quais são as limitações.


5

Também não sei ... meus programas Java são sempre lentos. :-) Eu nunca notei programas em C # sendo particularmente lentos, no entanto.


4

Aqui está outro ponto de referência interessante, que você pode experimentar no seu próprio computador.

Ele compara ASM, VC ++, C #, Silverlight, applet Java, Javascript, Flash (AS3)

Demonstração de velocidade do plugin Roozz

Observe que a velocidade do javascript varia muito, dependendo do navegador que está sendo executado. O mesmo vale para o Flash e o Silverlight, porque esses plug-ins são executados no mesmo processo que o navegador de hospedagem. Mas o plug-in Roozz executa arquivos .exe padrão, que são executados em seu próprio processo, portanto, a velocidade não é influenciada pelo navegador de hospedagem.


4

Você deve definir "executar melhor que ..". Bem, eu sei, você perguntou sobre velocidade, mas não é tudo o que conta.

  • As máquinas virtuais executam mais sobrecarga de tempo de execução? Sim!
  • Eles comem mais memória de trabalho? Sim!
  • Eles têm custos de inicialização mais altos (inicialização do tempo de execução e compilador JIT)? Sim!
  • Eles exigem uma enorme biblioteca instalada? Sim!

E assim por diante, é tendencioso, sim;)

Com C # e Java, você paga um preço pelo que recebe (codificação mais rápida, gerenciamento automático de memória, grande biblioteca e assim por diante). Mas você não tem muito espaço para discutir sobre os detalhes: pegue o pacote completo ou nada.

Mesmo que esses idiomas possam otimizar algum código para executar mais rápido que o código compilado, toda a abordagem é (IMHO) ineficiente. Imagine dirigir todos os dias 8 km até o local de trabalho, com um caminhão! É confortável, é bom, você está seguro (zona de dobra extrema) e, depois de pisar no acelerador por algum tempo, ele será tão rápido quanto um carro comum! Por que todos nós não temos um caminhão para dirigir para o trabalho? ;)

Em C ++, você obtém o que paga, nem mais, nem menos.

Citando Bjarne Stroustrup: "C ++ é minha linguagem favorita de coleta de lixo porque gera tão pouco lixo" link text


Bem, acho que ele tem uma boa idéia de suas desvantagens, ele também disse: "C facilita o tiro no pé; o C ++ torna mais difícil, mas quando você faz isso explode toda a perna";)
Frunsi

"Eles exigem uma enorme biblioteca instalada?" O Java está solucionando esse problema com o quebra-cabeças do projeto, acredito.
toc777

"Em C ++, você obtém o que paga, nem mais, nem menos". Exemplo de contrapartida: comparei uma implementação de árvore RB no OCaml e C ++ (GNU GCC) que usava uma exceção para sair da recursão se um elemento a ser adicionado já estivesse presente para reutilizar o conjunto existente. O OCaml foi até 6x mais rápido que o C ++ porque não paga pela verificação de destruidores, pois a pilha é desenrolada.
JD

3
@ Jon: mas em algum momento (mais tarde?) Ele tem que destruir os objetos de qualquer maneira (pelo menos ele precisa liberar sua memória). E observe também que as exceções são para casos excepcionais, pelo menos em C ++ essa regra deve ser respeitada. As exceções de C ++ podem ser pesadas quando ocorrem exceções, ou seja, uma compensação.
Frunsi 6/09/11

@ Jon: talvez tente repetir sua referência com timesuma concha. Para que ele verifique todo o programa, não apenas um aspecto. Os resultados são semelhantes então?
Frunsi 6/09/11

3

O código executável produzido a partir de um compilador Java ou C # não é interpretado - ele é compilado no código nativo "just in time" (JIT). Portanto, a primeira vez que o código em um programa Java / C # é encontrado durante a execução, há alguma sobrecarga, pois o "compilador de tempo de execução" (também conhecido como compilador JIT) transforma o código de bytes (Java) ou o código IL (C #) em instruções nativas da máquina. No entanto, na próxima vez em que o código for encontrado enquanto o aplicativo ainda estiver em execução, o código nativo será executado imediatamente. Isso explica como alguns programas Java / C # parecem estar lentos inicialmente, mas depois têm um desempenho melhor quanto mais tempo são executados. Um bom exemplo é um site ASP.Net. Na primeira vez em que o site é acessado, pode ser um pouco mais lento, pois o código C # é compilado no código nativo pelo compilador JIT.


3

Algumas boas respostas aqui sobre a pergunta específica que você fez. Eu gostaria de dar um passo atrás e olhar para a foto maior.

Lembre-se de que a percepção do usuário sobre a velocidade do software que você escreve é ​​afetada por muitos outros fatores além da otimização do codegen. aqui estão alguns exemplos:

  • O gerenciamento manual de memória é difícil de executar corretamente (sem vazamentos) e ainda mais difícil de executar com eficiência (libere memória logo após o término). Geralmente, o uso de um GC produz mais um programa que gerencia bem a memória. Você deseja trabalhar muito e atrasar a entrega do seu software, na tentativa de superar o GC?

  • Meu C # é mais fácil de ler e entender do que meu C ++. Também tenho mais maneiras de me convencer de que meu código C # está funcionando corretamente. Isso significa que eu posso otimizar meus algoritmos com menos risco de introduzir bugs (e os usuários não gostam de software que trava, mesmo que seja rápido!)

  • Posso criar meu software mais rapidamente em C # do que em C ++. Isso libera tempo para trabalhar no desempenho e ainda entrega meu software no prazo.

  • É mais fácil escrever uma boa interface do usuário em C # do que em C ++, por isso é mais provável que eu forme o trabalho em segundo plano enquanto a interface do usuário permanecer responsiva ou forneça progresso ou uma interface otimizada quando o programa for bloqueado por um tempo. Isso não torna nada mais rápido, mas deixa os usuários mais felizes com a espera.

Tudo o que eu disse sobre C # provavelmente é verdadeiro para Java, simplesmente não tenho a experiência para dizer com certeza.


3

Se você é um programador de Java / C # aprendendo C ++, ficará tentado a pensar em termos de Java / C # e traduzir literalmente a sintaxe C ++. Nesse caso, você obtém apenas os benefícios mencionados anteriormente do código nativo vs. interpretado / JIT. Para obter o maior ganho de desempenho em C ++ vs. Java / C #, você precisa aprender a pensar em C ++ e projetar código especificamente para explorar os pontos fortes do C ++.

Parafraseando Edsger Dijkstra : [sua primeira língua] mutila a mente além da recuperação.
Parafraseando Jeff Atwood : você pode escrever [seu primeiro idioma] em qualquer novo idioma.


1
Suspeito que o ditado "Você pode escrever FORTRAN em qualquer idioma" seja anterior à carreira de Jeff.
David Thornley

3

Uma das otimizações de JIT mais significativas é a inclusão de métodos. Java pode até incorporar métodos virtuais, se garantir a correção do tempo de execução. Esse tipo de otimização geralmente não pode ser executado por compiladores estáticos padrão, pois precisa de análise de programa inteiro, o que é difícil por causa da compilação separada (em contraste, o JIT tem todo o programa disponível). A inserção de métodos aprimora outras otimizações, fornecendo blocos de código maiores para otimizar.

A alocação de memória padrão em Java / C # também é mais rápida e a desalocação (GC) não é muito mais lenta, mas apenas menos determinística.


Note-se que freee deletenão são deterministas quer e GC podem ser feitas determinista por não alocar.
JD

3

É improvável que as linguagens de máquina virtual superem as linguagens compiladas, mas podem se aproximar o suficiente para que isso não importe, pelos (pelo menos) pelos seguintes motivos (estou falando de Java aqui, pois nunca fiz C #).

1 / O Java Runtime Environment geralmente é capaz de detectar trechos de código que são executados com freqüência e executar a compilação just-in-time (JIT) dessas seções para que, no futuro, elas sejam executadas na velocidade total compilada.

2 / Vastas partes das bibliotecas Java são compiladas para que, quando você chama uma função de biblioteca, esteja executando código compilado, não interpretado. Você pode ver o código (em C) baixando o OpenJDK.

3 / A menos que você esteja fazendo cálculos maciços, na maioria das vezes seu programa está sendo executado, ele está aguardando a entrada de um ser humano muito lento (relativamente falando).

4 / Como grande parte da validação do bytecode Java é feita no momento do carregamento da classe, a sobrecarga normal das verificações de tempo de execução é bastante reduzida.

5 / Na pior das hipóteses, o código com alto desempenho pode ser extraído para um módulo compilado e chamado do Java (consulte JNI) para que ele seja executado a toda velocidade.

Em resumo, o bytecode Java nunca superará a linguagem de máquina nativa, mas existem maneiras de mitigar isso. A grande vantagem do Java (a meu ver) é a biblioteca padrão ENORME e a natureza de plataforma cruzada.


1
No item 2, "2 / Vastas partes das bibliotecas Java são compiladas para que, quando você chama uma função de biblioteca, esteja executando um código compilado, não interpretado": Você tem uma citação para isso? Se fosse exatamente como você descreve, eu esperaria encontrar muito código nativo do meu depurador, mas não o faço.
cero

Re: cero Os depuradores geralmente utilizam caminhos menos eficientes, mas mais expressivos, e, portanto, não são um bom marcador para nada relacionado ao desempenho.
Guvante 7/10/08

2
Há outro grande ganho de desempenho nessa biblioteca HUGH - o código da biblioteca provavelmente é melhor escrito do que o que muitos programadores escreverão por conta própria (com tempo limitado e falta de conhecimento especializado) e no Java, por muitas razões, os programadores costumam usar a biblioteca.
Liran Orevi 03/12/2009

3

Orion Adrian , deixe-me inverter o seu post para ver como seus comentários são infundados, porque muito pode ser dito sobre C ++ também. E dizer que o compilador Java / C # otimiza funções vazias realmente faz você parecer que você não é meu especialista em otimização, porque a) por que um programa real deve conter funções vazias, exceto código legado muito ruim, b) que realmente não é otimização de borda preta e sangrenta.

Além dessa frase, você falou descaradamente sobre ponteiros, mas os objetos em Java e C # basicamente não funcionam como ponteiros em C ++? Eles não podem se sobrepor? Eles não podem ser nulos? C (e a maioria das implementações de C ++) tem a palavra-chave restringir, ambos têm tipos de valor, C ++ tem referência a valor com garantia não nula. O que Java e C # oferecem?

>>>>>>>>>>

Geralmente, C e C ++ podem ser tão rápidos ou mais rápidos porque o compilador AOT - um compilador que compila seu código antes da implantação, de uma vez por todas, na sua memória alta com muitos servidores de compilação principais - pode fazer otimizações que um programa compilado em C # não pode porque tem muito tempo para fazê-lo. O compilador pode determinar se a máquina é Intel ou AMD; Pentium 4, Core Solo ou Core Duo; ou se for compatível com SSE4, etc, e se o seu compilador não suportar o envio em tempo de execução, você poderá resolver isso implementando vários binários especializados.

O programa AC # geralmente é compilado ao executá-lo, para que decida bem em todas as máquinas, mas não é otimizado tanto quanto poderia ser para uma única configuração (por exemplo, processador, conjunto de instruções, outro hardware) e deve levar algum tempo primeiro. Recursos como fissão de loop, inversão de loop, vetorização automática, otimização de todo o programa, expansão de modelo, IPO e muito mais, são muito difíceis de serem resolvidos de maneira completa e completa de uma maneira que não incomode o usuário final.

Além disso, certos recursos de linguagem permitem que o compilador em C ++ ou C faça suposições sobre seu código, o que permite otimizar determinadas partes que não são seguras para o compilador Java / C #. Quando você não tem acesso ao ID completo do tipo de genéricos ou a um fluxo garantido de programa, há muitas otimizações que simplesmente não são seguras.

Além disso, C ++ e C fazem muitas alocações de pilha ao mesmo tempo com apenas um incremento de registro, o que certamente é mais eficiente que as alocações de Javas e C # quanto à camada de abstração entre o coletor de lixo e seu código.

Agora não posso falar sobre Java neste próximo ponto, mas sei que os compiladores C ++, por exemplo, removerão métodos e chamadas de métodos quando souberem que o corpo do método está vazio, eliminará subexpressões comuns e tentarão tentar novamente. para encontrar o uso ideal do registro, ele não impõe a verificação de limites, ele autovectoriza loops e loops internos e inverte de interno para externo, move condicionais para fora dos loops, divide e descomplica loops. Ele expandirá std :: vector em matrizes de sobrecarga zero nativas, como você faria da maneira C. Ele fará otimizações entre procedimentos. Ele construirá valores de retorno diretamente no site do chamador. Dobra e propaga expressões. Reordenará os dados de maneira amigável ao cache. Isso fará a passagem de salto. Ele permite que você escreva rastreadores de raio de tempo de compilação com zero tempo de execução. Isso fará otimizações baseadas em gráficos muito caras. Isso reduzirá a força, caso substitua certos códigos por códigos sintaticamente totalmente desiguais, mas semanticamente equivalentes (o antigo "xor foo, foo" é a otimização mais simples, porém desatualizada). Se você desejar, pode omitir os padrões de ponto flutuante IEEE e habilitar ainda mais otimizações, como reordenar o operando de ponto flutuante. Depois de massagear e massacrar seu código, ele pode repetir todo o processo, porque muitas vezes certas otimizações estabelecem as bases para otimizações ainda mais certas. Também pode tentar novamente com os parâmetros aleatórios e ver como a outra variante obtém pontuação em sua classificação interna. E ele usará esse tipo de lógica em todo o seu código. caso substitua certos códigos por códigos sintaticamente totalmente desiguais, mas semanticamente equivalentes (o antigo "xor foo, foo" é apenas a otimização mais simples, embora desatualizada). Se você desejar, pode omitir os padrões de ponto flutuante IEEE e habilitar ainda mais otimizações, como reordenar o operando de ponto flutuante. Depois de massagear e massacrar seu código, ele pode repetir todo o processo, porque muitas vezes certas otimizações estabelecem as bases para otimizações ainda mais certas. Também pode tentar novamente com os parâmetros aleatórios e ver como a outra variante obtém pontuação em sua classificação interna. E ele usará esse tipo de lógica em todo o seu código. caso substitua certos códigos por códigos sintaticamente totalmente desiguais, mas semanticamente equivalentes (o antigo "xor foo, foo" é apenas a otimização mais simples, embora desatualizada). Se você desejar, pode omitir os padrões de ponto flutuante IEEE e habilitar ainda mais otimizações, como reordenar o operando de ponto flutuante. Depois de massagear e massacrar seu código, ele pode repetir todo o processo, porque muitas vezes certas otimizações estabelecem as bases para otimizações ainda mais certas. Também pode tentar novamente com os parâmetros aleatórios e ver como a outra variante obtém pontuação em sua classificação interna. E ele usará esse tipo de lógica em todo o seu código. Se você desejar, pode omitir os padrões de ponto flutuante IEEE e habilitar ainda mais otimizações, como reordenar o operando de ponto flutuante. Depois de massagear e massacrar seu código, ele pode repetir todo o processo, porque muitas vezes certas otimizações estabelecem as bases para otimizações ainda mais certas. Também pode tentar novamente com os parâmetros aleatórios e ver como a outra variante obtém pontuação em sua classificação interna. E ele usará esse tipo de lógica em todo o seu código. Se você desejar, pode omitir os padrões de ponto flutuante IEEE e habilitar ainda mais otimizações, como reordenar o operando de ponto flutuante. Depois de massagear e massacrar seu código, ele pode repetir todo o processo, porque muitas vezes certas otimizações estabelecem as bases para otimizações ainda mais certas. Também pode tentar novamente com os parâmetros aleatórios e ver como a outra variante obtém pontuação em sua classificação interna. E ele usará esse tipo de lógica em todo o seu código. Também pode tentar novamente com os parâmetros aleatórios e ver como a outra variante obtém pontuação em sua classificação interna. E ele usará esse tipo de lógica em todo o seu código. Também pode tentar novamente com os parâmetros aleatórios e ver como a outra variante obtém pontuação em sua classificação interna. E ele usará esse tipo de lógica em todo o seu código.

Portanto, como você pode ver, existem várias razões pelas quais determinadas implementações em C ++ ou C serão mais rápidas.

Agora, dito isso, muitas otimizações podem ser feitas em C ++ que explodirão tudo o que você poderia fazer com C #, especialmente no domínio de processamento de números, em tempo real e próximo ao metal, mas não exclusivamente lá. Você nem precisa tocar em um ponteiro para percorrer um longo caminho.

Então, dependendo do que você está escrevendo, eu iria com um ou outro. Mas se você estiver escrevendo algo que não depende de hardware (driver, videogame etc.), não me preocuparia com o desempenho do C # (novamente não posso falar sobre Java). Vai dar certo.

<<<<<<<<<<<

Geralmente, certos argumentos generalizados podem parecer interessantes em postagens específicas, mas geralmente não parecem certamente confiáveis.

De qualquer forma, para fazer as pazes: AOT é ótimo, assim como o JIT . A única resposta correta pode ser: Depende. E as pessoas realmente inteligentes sabem que você pode usar o melhor dos dois mundos de qualquer maneira.


2

Isso aconteceria apenas se o intérprete Java estiver produzindo um código de máquina que seja realmente melhor otimizado do que o código de máquina que seu compilador está gerando para o código C ++ que você está escrevendo, até o ponto em que o código C ++ é mais lento que o Java e o custo de interpretação.

No entanto, as chances de que isso aconteça são bastante baixas - a menos que talvez o Java tenha uma biblioteca muito bem escrita e você tenha sua própria biblioteca C ++ mal escrita.


Eu também acredito que também existe um certo peso de linguagem. Ao trabalhar em um nível inferior, com menos abstração, você estará desenvolvendo um programa mais rápido. Isso não tem relação com os pontos sobre a própria execução do bytecode.
Brian R. Bondy

2

Na verdade, o C # realmente não é executado em uma máquina virtual como o Java. O IL é compilado na linguagem assembly, que é um código totalmente nativo e é executada na mesma velocidade que o código nativo. Você pode pré-JIT um aplicativo .NET que remove completamente o custo do JIT e, em seguida, executa um código totalmente nativo.

A desaceleração com o .NET não ocorrerá porque o código .NET é mais lento, mas porque faz muito mais nos bastidores para fazer coisas como coleta de lixo, verificação de referências, armazenamento de quadros de pilha completos etc. Isso pode ser bastante poderoso e útil quando aplicativos de construção, mas também tem um custo. Observe que você também pode fazer todas essas coisas em um programa C ++ (grande parte da funcionalidade .NET principal é na verdade código .NET que você pode exibir no ROTOR). No entanto, se você escrevesse manualmente a mesma funcionalidade, provavelmente terminaria com um programa muito mais lento, pois o tempo de execução do .NET foi otimizado e ajustado com precisão.

Dito isto, um dos pontos fortes do código gerenciado é que ele pode ser totalmente verificável, ou seja. você pode verificar se o código nunca acessará a memória de outros processos ou fará coisas indesejadas antes de executá-lo. A Microsoft tem um protótipo de pesquisa de um sistema operacional totalmente gerenciado que mostrou surpreendentemente que um ambiente 100% gerenciado pode realmente executar significativamente mais rapidamente do que qualquer sistema operacional moderno, aproveitando essa verificação para desativar os recursos de segurança que não são mais necessários aos programas gerenciados (estamos falando em 10x em alguns casos). A rádio SE tem um ótimo episódio falando sobre esse projeto.


1

Em alguns casos, o código gerenciado pode realmente ser mais rápido que o código nativo. Por exemplo, os algoritmos de coleta de lixo "marcar e varrer" permitem que ambientes como o JRE ou CLR liberem um grande número de objetos de vida curta (geralmente) em uma única passagem, onde a maioria dos objetos de heap C / C ++ são liberados um a um. um tempo.

Da wikipedia :

Para muitos propósitos práticos, os algoritmos intensivos de alocação / desalocação implementados em idiomas coletores de lixo podem ser realmente mais rápidos que seus equivalentes usando a alocação manual de heap. Um dos principais motivos disso é que o coletor de lixo permite que o sistema de tempo de execução amortize as operações de alocação e desalocação de uma maneira potencialmente vantajosa.

Dito isso, escrevi muito C # e muito C ++, além de executar muitos benchmarks. Na minha experiência, o C ++ é muito mais rápido que o C #, de duas maneiras: (1) se você pegar algum código que você escreveu em C #, portá-lo para C ++, o código nativo tende a ser mais rápido. Quão rápido? Bem, isso varia muito, mas não é incomum ver uma melhoria de velocidade de 100%. (2) Em alguns casos, a coleta de lixo pode desacelerar bastante um aplicativo gerenciado. O .NET CLR faz um trabalho terrível com grandes montantes (por exemplo,> 2 GB) e pode acabar gastando muito tempo no GC - mesmo em aplicativos que possuem poucos ou nenhum objeto de vida útil intermediária.

Obviamente, na maioria dos casos que contei, as linguagens gerenciadas são rápidas o suficiente, e a troca de manutenção e codificação pelo desempenho extra do C ++ simplesmente não é boa.


1
O problema é que, para processos de execução longa, como um servidor Web, sua memória com o tempo se torna tão fragmentada (em um programa gravado em C ++) que você precisará implementar algo que se assemelha à coleta de lixo (ou reiniciar de vez em quando, consulte IIS). )
Tony BenBrahim 28/09/08

3
Eu não observei isso nos grandes programas Unix que deveriam funcionar para sempre. Eles tendem a ser escritos em C, o que é ainda pior para o gerenciamento de memória que o C ++.
21730 David Thornley

Obviamente, a questão é se estamos comparando a implementação de um programa em código gerenciado versus código não gerenciado ou o desempenho teórico superior do idioma. Claramente, o código não gerenciado sempre pode ser pelo menos tão rápido quanto gerenciado, pois, na pior das hipóteses, você poderia escrever um programa não gerenciado que fizesse exatamente a mesma coisa que o código gerenciado! Mas a maioria dos problemas de desempenho é algorítmica, não micro. Além disso, você não otimiza o código gerenciado e não gerenciado da mesma maneira; portanto, "C ++ em C #" geralmente não funciona bem.
Kyoryu

2
No C / C ++, você pode alocar objetos de vida curta na pilha e o faz quando apropriado. No código gerenciado, você não pode , não tem escolha. Além disso, em C / C ++, você pode alocar listas de objetos em áreas contíguas (novo Foo [100]), no código gerenciado não pode. Portanto, sua comparação não é válida. Bem, esse poder de escolha sobrecarrega os desenvolvedores, mas assim eles aprendem a conhecer o mundo em que vivem (memória ......).
Frunsi 6/12/09

@frunsi: "em C / C ++ você pode alocar listas de objetos em áreas contíguas (novo Foo [100]), no código gerenciado você não pode". Isso está incorreto. Os tipos de valor local são alocados por pilha e você pode até alocar matrizes deles em C #. Existem até sistemas de produção escritos em C # que são completamente sem alocação no estado estacionário.
JD


1

Na verdade, o HotSpot JVM da Sun usa execução em "modo misto". Ele interpreta o bytecode do método até determinar (geralmente através de um contador de algum tipo) que um determinado bloco de código (método, loop, bloco try-catch, etc.) será executado muito, e então o JIT o compila. O tempo necessário para compilar um método JIT geralmente leva mais tempo do que se o método fosse interpretado se for um método raramente executado. O desempenho geralmente é mais alto para o "modo misto" porque a JVM não perde tempo com o código JIT que raramente é executado, se é que alguma vez é executado. C # e .NET não fazem isso. O .NET JITs tudo o que, muitas vezes, desperdiça tempo.


1

Leia sobre o Dynamo do HP Labs , um intérprete para o PA-8000 que roda no PA-8000 e geralmente executa programas mais rapidamente do que o original. Então não vai parecer nada surpreendente!

Não pense nisso como uma "etapa intermediária" - a execução de um programa já envolve muitas outras etapas, em qualquer idioma.

Geralmente se resume a:

  • os programas têm pontos de acesso, portanto, mesmo se você estiver executando mais lentamente 95% do corpo do código que precisa executar, ainda poderá ser competitivo em termos de desempenho se for mais rápido nos 5% quentes

  • um HLL sabe mais sobre sua intenção do que um LLL como C / C ++ e, portanto, pode gerar um código mais otimizado (o OCaml tem ainda mais e, na prática, geralmente é ainda mais rápido)

  • um compilador JIT possui muitas informações que um compilador estático não possui (como os dados reais que você possui neste momento)

  • um compilador JIT pode fazer otimizações em tempo de execução que os vinculadores tradicionais não têm permissão para fazer (como reordenar ramificações para que o caso comum seja simples ou chamadas de biblioteca embutidas)

Em suma, o C / C ++ é uma linguagem bastante ruim para o desempenho: há relativamente poucas informações sobre seus tipos de dados, nenhuma informação sobre eles e nenhum tempo de execução dinâmico para permitir muita otimização em tempo de execução.


1

Você pode obter rajadas curtas quando Java ou CLR é mais rápido que C ++, mas, em geral, o desempenho é pior para a vida útil do aplicativo: consulte www.codeproject.com/KB/dotnet/RuntimePerformance.aspx para obter alguns resultados.



1

Meu entendimento é que o C / C ++ produz código nativo para executar em uma arquitetura de máquina específica. Por outro lado, linguagens como Java e C # são executadas em cima de uma máquina virtual que abstrai a arquitetura nativa. Logicamente, parece impossível para Java ou C # corresponder à velocidade do C ++ devido a esta etapa intermediária, no entanto, me disseram que os compiladores mais recentes ("hot spot") podem atingir essa velocidade ou até excedê-la.

Isso é ilógico. O uso de uma representação intermediária não prejudica inerentemente o desempenho. Por exemplo, o llvm-gcc compila C e C ++ via LLVM IR (que é uma máquina virtual de registro infinito) para código nativo e alcança excelente desempenho (geralmente superando o GCC).

Talvez seja mais uma questão de compilador do que de idioma, mas alguém pode explicar em inglês simples como é possível que um desses idiomas de máquina virtual tenha um desempenho melhor que o idioma nativo?

aqui estão alguns exemplos:

  • Máquinas virtuais com compilação JIT facilitam a geração de código em tempo de execução (por exemplo, System.Reflection.Emitno .NET) para que você possa compilar o código gerado rapidamente em linguagens como C # e F #, mas deve recorrer à gravação de um interpretador comparativamente lento em C ou C ++. Por exemplo, para implementar expressões regulares.

  • Partes da máquina virtual (por exemplo, a barreira de gravação e o alocador) geralmente são escritas no assembler codificado à mão, porque C e C ++ não geram código rápido o suficiente. Se um programa enfatizar essas partes de um sistema, é possível que supere qualquer coisa que possa ser escrita em C ou C ++.

  • A vinculação dinâmica de código nativo requer conformidade com uma ABI que pode impedir o desempenho e obvia a otimização de todo o programa, enquanto a vinculação é normalmente adiada em VMs e pode se beneficiar de otimizações de todo o programa (como os genéricos reificados do .NET).

Eu também gostaria de resolver alguns problemas com a resposta altamente votada de paercebal acima (porque alguém continua excluindo meus comentários em sua resposta) que apresenta uma visão polarizada contraproducente:

O processamento do código será feito no momento da compilação ...

Portanto, a metaprogramação de modelos funciona apenas se o programa estiver disponível em tempo de compilação, o que geralmente não é o caso, por exemplo, é impossível escrever uma biblioteca de expressões regulares com desempenho competitivo em vanilla C ++ porque é incapaz de gerar código de tempo de execução (um aspecto importante do metaprogramação).

... jogar com tipos é feito em tempo de compilação ... o equivalente em Java ou C # é doloroso na melhor das hipóteses, e sempre será mais lento e resolvido em tempo de execução, mesmo quando os tipos são conhecidos em tempo de compilação.

Em C #, isso vale apenas para tipos de referência e não para tipos de valor.

Não importa a otimização do JIT, nada será rápido como acesso direto ao ponteiro da memória ... se você tiver dados contíguos na memória, acessá-los por meio de ponteiros C ++ (ou seja, ponteiros C ... Vamos dar a Caesar o que lhe é devido) será mais rápido do que em Java / C #.

As pessoas observaram o Java batendo o C ++ no teste SOR a partir do benchmark SciMark2 precisamente porque os ponteiros impedem otimizações relacionadas a aliases.

Também é importante notar que o .NET digita a especialização de genéricos nas bibliotecas vinculadas dinamicamente após a vinculação, enquanto o C ++ não pode porque os modelos devem ser resolvidos antes da vinculação. E, obviamente, a grande vantagem que os genéricos têm sobre os modelos são as mensagens de erro compreensíveis.


0

Além do que outros disseram, do meu entendimento, .NET e Java são melhores na alocação de memória. Por exemplo, eles podem compactar a memória à medida que ela é fragmentada, enquanto o C ++ não pode (nativamente, mas pode se você estiver usando um coletor de lixo inteligente).


Ou se você estiver usando um alocador C ++ e / ou pool de objetos melhor. Isso está longe de ser mágico, do ponto de vista do C ++, e pode se resumir a fazer com que a "alocação de heap" se torne tão rápida quanto a alocação de pilha.
paercebal

Se você sempre alocasse tudo no heap, o .NET e Java podem até ter um desempenho melhor que o C / C ++. Mas você simplesmente não fará isso em C / C ++.
Frunsi 6/12/09

0

Para qualquer coisa que precise de muita velocidade, a JVM apenas chama uma implementação de C ++, por isso é uma questão mais de quão boas são suas bibliotecas do que quão boa é a JVM para a maioria das coisas relacionadas ao SO. A coleta de lixo reduz sua memória pela metade, mas o uso de alguns dos recursos mais sofisticados de STL e Boost terá o mesmo efeito, mas com muitas vezes o potencial de erro.

Se você estiver apenas usando bibliotecas C ++ e muitos de seus recursos de alto nível em um projeto grande com muitas classes, provavelmente acabará mais devagar do que usando uma JVM. Exceto muito mais propenso a erros.

No entanto, o benefício do C ++ é que ele permite que você se otimize, caso contrário, você fica preso ao que o compilador / jvm faz. Se você criar seus próprios contêineres, escrever seu próprio gerenciamento de memória alinhado, usar o SIMD e soltar para montagem aqui e ali, poderá acelerar pelo menos 2x-4x vezes o que a maioria dos compiladores C ++ fará por conta própria. Para algumas operações, 16x-32x. É usar os mesmos algoritmos; se você usar algoritmos melhores e paralelizar, os aumentos podem ser dramáticos, às vezes milhares de vezes mais rápidos que os métodos comumente usados.


0

Eu olho para isso de alguns pontos diferentes.

  1. Com tempo e recursos infinitos, o código gerenciado ou não gerenciado será mais rápido? Claramente, a resposta é que o código não gerenciado sempre pode, pelo menos, vincular o código gerenciado nesse aspecto - como no pior caso, você apenas codificaria a solução do código gerenciado.
  2. Se você pega um programa em um idioma e o traduz diretamente para outro, quanto pior será o desempenho? Provavelmente muito, por quaisquer dois idiomas. A maioria dos idiomas requer otimizações diferentes e possui diferentes dicas. O micro-desempenho geralmente é muito importante para conhecer esses detalhes.
  3. Dado tempo e recursos finitos, qual dos dois idiomas produzirá um resultado melhor? Essa é a pergunta mais interessante, pois enquanto uma linguagem gerenciada pode produzir código um pouco mais lento (dado um programa razoavelmente escrito para essa linguagem), essa versão provavelmente será feita mais cedo, permitindo mais tempo gasto em otimização.

0

Uma resposta muito curta: com um orçamento fixo, você obterá um aplicativo java com melhor desempenho do que um aplicativo C ++ (considerações sobre ROI). Além disso, a plataforma Java possui perfis mais decentes, que ajudarão você a identificar seus pontos de acesso mais rapidamente

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.