Estou descobrindo enormes diferenças de desempenho entre códigos semelhantes em C anc C #.
O código C é:
#include <stdio.h>
#include <time.h>
#include <math.h>
main()
{
int i;
double root;
clock_t start = clock();
for (i = 0 ; i <= 100000000; i++){
root = sqrt(i);
}
printf("Time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
}
E o C # (aplicativo de console) é:
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
DateTime startTime = DateTime.Now;
double root;
for (int i = 0; i <= 100000000; i++)
{
root = Math.Sqrt(i);
}
TimeSpan runTime = DateTime.Now - startTime;
Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds/1000));
}
}
}
Com o código acima, o C # é concluído em 0,328125 segundos (versão de lançamento) e o C leva 11,14 segundos para ser executado.
OC está sendo compilado para um executável do Windows usando o mingw.
Sempre presumi que C / C ++ era mais rápido ou pelo menos comparável a C # .net. O que exatamente está fazendo o C funcionar 30 vezes mais lento?
EDITAR: Parece que o otimizador C # estava removendo a raiz porque ela não estava sendo usada. Mudei a atribuição de raiz para root + = e imprimi o total no final. Também compilei o C usando cl.exe com o sinalizador / O2 definido para velocidade máxima.
Os resultados são agora: 3,75 segundos para o C 2.61 segundos para o C #
OC ainda está demorando mais, mas isso é aceitável
fonte
Respostas:
Como você nunca usa 'root', o compilador pode ter removido a chamada para otimizar seu método.
Você pode tentar acumular os valores da raiz quadrada em um acumulador, imprimi-lo no final do método e ver o que está acontecendo.
Editar: veja a resposta de Jalf abaixo
fonte
Você deve comparar compilações de depuração. Acabei de compilar seu código C e
Se você não habilitar as otimizações, qualquer benchmarking que você fizer será completamente inútil. (E se você habilitar as otimizações, o loop é otimizado. Portanto, seu código de benchmarking também apresenta falhas. Você precisa forçá-lo a executar o loop, geralmente somando o resultado ou algo semelhante e imprimindo-o no final)
Parece que o que você está medindo é basicamente "qual compilador insere a maior sobrecarga de depuração". E a resposta é C. Mas isso não nos diz qual programa é mais rápido. Porque quando você quer velocidade, você habilita otimizações.
A propósito, você evitará muitas dores de cabeça a longo prazo se abandonar qualquer noção de que as línguas são "mais rápidas" que as outras. C # não tem mais velocidade do que o inglês.
Há certas coisas na linguagem C que seriam eficientes mesmo em um compilador ingênuo e não otimizador, e há outras que dependem muito de um compilador para otimizar tudo. E, claro, o mesmo vale para C # ou qualquer outra linguagem.
A velocidade de execução é determinada por:
Um bom compilador C # produzirá um código eficiente. Um compilador C ruim gerará código lento. Que tal um compilador C que gerou código C #, que você poderia executar por meio de um compilador C #? Quão rápido isso funcionaria? Os idiomas não têm velocidade. Seu código sim.
fonte
i
esqrt
, portanto, é isso que está sendo medido.Vou ser breve, já está marcado como respondido. C # tem a grande vantagem de ter um modelo de ponto flutuante bem definido. Isso simplesmente coincide com o modo de operação nativo das instruções FPU e SSE definidas nos processadores x86 e x64. Nenhuma coincidência aí. O JITter compila Math.Sqrt () para algumas instruções embutidas.
C / C ++ nativo está sobrecarregado com anos de compatibilidade com versões anteriores. As opções de compilação / fp: precise, / fp: fast e / fp: strict são as mais visíveis. Da mesma forma, ele deve chamar uma função CRT que implementa sqrt () e verifica as opções de ponto flutuante selecionadas para ajustar o resultado. Isso é lento.
fonte
Sou um desenvolvedor C ++ e C #. Tenho desenvolvido aplicativos C # desde a primeira versão beta do .NET framework e tenho mais de 20 anos de experiência no desenvolvimento de aplicativos C ++. Em primeiro lugar, o código C # NUNCA será mais rápido do que um aplicativo C ++, mas não vou passar por uma longa discussão sobre o código gerenciado, como ele funciona, a camada de interoperabilidade, componentes internos de gerenciamento de memória, o sistema de tipo dinâmico e o coletor de lixo. No entanto, deixe-me continuar dizendo que todos os benchmarks listados aqui produzem resultados INCORRETOS.
Deixe-me explicar: a primeira coisa que precisamos considerar é o compilador JIT para C # (.NET Framework 4). Agora, o JIT produz código nativo para a CPU usando vários algoritmos de otimização (que tendem a ser mais agressivos do que o otimizador C ++ padrão que vem com o Visual Studio) e o conjunto de instruções usado pelo compilador .NET JIT é um reflexo mais próximo da CPU real na máquina, de modo que certas substituições no código da máquina possam ser feitas para reduzir os ciclos de clock e melhorar a taxa de acerto no cache do pipeline da CPU e produzir mais otimizações de hyper-threading, como reordenamento de instruções e melhorias relacionadas à previsão de ramificação.
O que isso significa é que, a menos que você compile seu aplicativo C ++ usando os parâmetros corretos para o build RELEASE (não o build DEBUG), seu aplicativo C ++ pode executar mais lentamente do que o aplicativo baseado em C # ou .NET correspondente. Ao especificar as propriedades do projeto em seu aplicativo C ++, certifique-se de habilitar "otimização total" e "favorecer código rápido". Se você tiver uma máquina de 64 bits, você DEVE especificar gerar x64 como a plataforma de destino, caso contrário, seu código será executado por meio de uma subcamada de conversão (WOW64) que reduzirá substancialmente o desempenho.
Depois de executar as otimizações corretas no compilador, obtenho 0,72 segundo para o aplicativo C ++ e 1,16 segundo para o aplicativo C # (ambos na versão de compilação). Como o aplicativo C # é muito básico e aloca a memória usada no loop na pilha e não no heap, ele está, na verdade, tendo um desempenho muito melhor do que um aplicativo real envolvido em objetos, cálculos pesados e com conjuntos de dados maiores. Portanto, os números fornecidos são números otimistas tendenciosos para C # e a estrutura .NET. Mesmo com essa tendência, o aplicativo C ++ é concluído em pouco mais da metade do tempo do que o aplicativo C # equivalente. Lembre-se de que o compilador Microsoft C ++ que usei não tinha o pipeline correto e as otimizações de hyperthreading (usando WinDBG para visualizar as instruções de montagem).
Agora, se usarmos o compilador Intel (que, a propósito, é um segredo da indústria para gerar aplicativos de alto desempenho em processadores AMD / Intel), o mesmo código será executado em 0,54 segundos para o executável C ++ versus 0,72 segundos usando o Microsoft Visual Studio 2010 . Portanto, no final, os resultados finais são 0,54 segundos para C ++ e 1,16 segundos para C #. Portanto, o código produzido pelo compilador .NET JIT leva 214% mais tempo do que o executável C ++. A maior parte do tempo gasto nos 0,54 segundos foi para obter o tempo do sistema e não dentro do próprio loop!
O que também está faltando nas estatísticas são os tempos de inicialização e limpeza, que não estão incluídos nas temporizações. Os aplicativos C # tendem a gastar muito mais tempo na inicialização e no encerramento do que os aplicativos C ++. A razão por trás disso é complicada e tem a ver com as rotinas de validação de código em tempo de execução .NET e o subsistema de gerenciamento de memória que executa muito trabalho no início (e, conseqüentemente, no final) do programa para otimizar as alocações de memória e o lixo colecionador.
Ao medir o desempenho de C ++ e .NET IL, é importante observar o código do assembly para ter certeza de que TODOS os cálculos estão lá. O que descobri é que, sem colocar algum código adicional em C #, a maior parte do código nos exemplos acima foi realmente removida do binário. Este também foi o caso com C ++ quando você usou um otimizador mais agressivo, como o que vem com o compilador Intel C ++. Os resultados que forneci acima são 100% corretos e validados no nível de montagem.
O principal problema com muitos fóruns na internet é que muitos novatos ouvem a propaganda de marketing da Microsoft sem entender a tecnologia e fazem falsas alegações de que C # é mais rápido que C ++. A alegação é que, em teoria, C # é mais rápido que C ++ porque o compilador JIT pode otimizar o código para a CPU. O problema com essa teoria é que existem muitos encanamentos na estrutura .NET que tornam o desempenho mais lento; encanamento que não existe no aplicativo C ++. Além disso, um desenvolvedor experiente saberá o compilador certo a ser usado para a plataforma fornecida e usará os sinalizadores apropriados ao compilar o aplicativo. Nas plataformas Linux ou de código aberto, isso não é um problema porque você pode distribuir sua origem e criar scripts de instalação que compilam o código usando a otimização apropriada. Na plataforma Windows ou de código fechado, você terá que distribuir vários executáveis, cada um com otimizações específicas. Os binários do Windows que serão implantados são baseados na CPU detectada pelo instalador msi (usando ações personalizadas).
fonte
meu primeiro palpite é uma otimização do compilador porque você nunca usa root. Você apenas atribui e sobrescreve novamente e novamente.
Edit: droga, venceu por 9 segundos!
fonte
Para ver se o loop está sendo otimizado, tente alterar seu código para
ans da mesma forma no código C, e então imprimir o valor de root fora do loop.
fonte
Talvez o compilador c # esteja percebendo que você não usa root em nenhum lugar, então ele simplesmente pula todo o loop for. :)
Pode não ser o caso, mas suspeito que seja qual for a causa, é dependente da implementação do compilador. Tente compilar seu programa C com o compilador Microsoft (cl.exe, disponível como parte do win32 sdk) com otimizações e modo de lançamento. Aposto que você verá uma melhoria de desempenho em relação ao outro compilador.
EDIT: Eu não acho que o compilador pode apenas otimizar o loop for, porque ele teria que saber que Math.Sqrt () não tem efeitos colaterais.
fonte
Seja qual for o tempo diff. pode ser, esse "tempo decorrido" é inválido. Só seria válido se você pudesse garantir que ambos os programas rodariam exatamente nas mesmas condições.
Talvez você devesse tentar uma vitória. equivalente a $ / usr / bin / time my_cprog; / usr / bin / time my_csprog
fonte
Eu reuni (com base em seu código) mais dois testes comparáveis em C e C #. Esses dois escrevem uma matriz menor usando o operador de módulo para indexação (adiciona um pouco de overhead, mas, ei, estamos tentando comparar o desempenho [em um nível bruto]).
Código C:
Em C #:
Esses testes gravam dados em um array (portanto, o tempo de execução do .NET não deve ter permissão para eliminar o sqrt op), embora o array seja significativamente menor (não queria usar memória excessiva). Compilei-os na configuração de lançamento e executei-os de dentro de uma janela de console (em vez de começar pelo VS).
No meu computador, o programa C # varia entre 6,2 e 6,9 segundos, enquanto a versão C varia entre 6,9 e 7.1.
fonte
Se você fizer uma única etapa do código no nível do assembly, incluindo a etapa de rotina da raiz quadrada, provavelmente obterá a resposta para sua pergunta.
Não há necessidade de adivinhação educada.
fonte
O outro fator que pode ser um problema aqui é que o compilador C compila para o código nativo genérico para a família de processadores que você almeja, enquanto o MSIL gerado quando você compilou o código C # é então JIT compilado para atingir o processador exato que você completou com qualquer otimizações que podem ser possíveis. Portanto, o código nativo gerado a partir do C # pode ser consideravelmente mais rápido do que o C.
fonte
Parece-me que isso não tem nada a ver com as próprias linguagens, mas sim com as diferentes implementações da função de raiz quadrada.
fonte
Na verdade pessoal, o loop NÃO está sendo otimizado. Compilei o código de John e examinei o .exe resultante. As entranhas do loop são as seguintes:
A menos que o tempo de execução seja inteligente o suficiente para perceber que o loop não faz nada e o pula?
Edit: Alterando o C # para ser:
Resulta no tempo decorrido (na minha máquina) indo de 0,047 a 2,17. Mas isso é apenas a sobrecarga de adicionar 100 milhões de operadores adicionais?
fonte