Eu gosto de alguns recursos do D, mas estaria interessado se eles vierem com uma penalidade de tempo de execução?
Para comparar, implementei um programa simples que calcula produtos escalares de muitos vetores curtos, tanto em C ++ quanto em D. O resultado é surpreendente:
- D: 18,9 s [veja abaixo o tempo de execução final]
- C ++: 3,8 s
C ++ é realmente quase cinco vezes mais rápido ou cometi um erro no programa D?
Compilei C ++ com g ++ -O3 (gcc-snapshot 2011-02-19) e D com dmd -O (dmd 2.052) em uma área de trabalho Linux recente moderada. Os resultados são reproduzíveis em várias execuções e os desvios padrão são insignificantes.
Aqui o programa C ++:
#include <iostream>
#include <random>
#include <chrono>
#include <string>
#include <vector>
#include <array>
typedef std::chrono::duration<long, std::ratio<1, 1000>> millisecs;
template <typename _T>
long time_since(std::chrono::time_point<_T>& time) {
long tm = std::chrono::duration_cast<millisecs>( std::chrono::system_clock::now() - time).count();
time = std::chrono::system_clock::now();
return tm;
}
const long N = 20000;
const int size = 10;
typedef int value_type;
typedef long long result_type;
typedef std::vector<value_type> vector_t;
typedef typename vector_t::size_type size_type;
inline value_type scalar_product(const vector_t& x, const vector_t& y) {
value_type res = 0;
size_type siz = x.size();
for (size_type i = 0; i < siz; ++i)
res += x[i] * y[i];
return res;
}
int main() {
auto tm_before = std::chrono::system_clock::now();
// 1. allocate and fill randomly many short vectors
vector_t* xs = new vector_t [N];
for (int i = 0; i < N; ++i) {
xs[i] = vector_t(size);
}
std::cerr << "allocation: " << time_since(tm_before) << " ms" << std::endl;
std::mt19937 rnd_engine;
std::uniform_int_distribution<value_type> runif_gen(-1000, 1000);
for (int i = 0; i < N; ++i)
for (int j = 0; j < size; ++j)
xs[i][j] = runif_gen(rnd_engine);
std::cerr << "random generation: " << time_since(tm_before) << " ms" << std::endl;
// 2. compute all pairwise scalar products:
time_since(tm_before);
result_type avg = 0;
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
avg += scalar_product(xs[i], xs[j]);
avg = avg / N*N;
auto time = time_since(tm_before);
std::cout << "result: " << avg << std::endl;
std::cout << "time: " << time << " ms" << std::endl;
}
E aqui a versão D:
import std.stdio;
import std.datetime;
import std.random;
const long N = 20000;
const int size = 10;
alias int value_type;
alias long result_type;
alias value_type[] vector_t;
alias uint size_type;
value_type scalar_product(const ref vector_t x, const ref vector_t y) {
value_type res = 0;
size_type siz = x.length;
for (size_type i = 0; i < siz; ++i)
res += x[i] * y[i];
return res;
}
int main() {
auto tm_before = Clock.currTime();
// 1. allocate and fill randomly many short vectors
vector_t[] xs;
xs.length = N;
for (int i = 0; i < N; ++i) {
xs[i].length = size;
}
writefln("allocation: %i ", (Clock.currTime() - tm_before));
tm_before = Clock.currTime();
for (int i = 0; i < N; ++i)
for (int j = 0; j < size; ++j)
xs[i][j] = uniform(-1000, 1000);
writefln("random: %i ", (Clock.currTime() - tm_before));
tm_before = Clock.currTime();
// 2. compute all pairwise scalar products:
result_type avg = cast(result_type) 0;
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
avg += scalar_product(xs[i], xs[j]);
avg = avg / N*N;
writefln("result: %d", avg);
auto time = Clock.currTime() - tm_before;
writefln("scalar products: %i ", time);
return 0;
}
c++
performance
runtime
d
Lars
fonte
fonte
avg = avg / N*N
(ordem das operações).dmd ... trace.def
que recebo umerror: unrecognized file extension def
. E os documentos dmd para optlink mencionam apenas o Windows.Respostas:
Para ativar todas as otimizações e desativar todas as verificações de segurança, compile seu programa D com os seguintes sinalizadores DMD:
Edição : Eu tentei seus programas com g ++, dmd e gdc. O dmd fica para trás, mas o gdc alcança um desempenho muito próximo ao g ++. A linha de comando que eu usei era
gdmd -O -release -inline
(gdmd é um wrapper em torno do gdc que aceita opções de dmd).Observando a lista do assembler, parece que nem o dmd nem o gdc estão alinhados
scalar_product
, mas o g ++ / gdc emitiu instruções MMX, portanto, eles podem auto-vetorizar o loop.fonte
Uma grande coisa que desacelera o D é uma implementação de coleta de lixo abaixo da média. Os benchmarks que não enfatizam muito o GC mostrarão desempenho muito semelhante ao código C e C ++ compilado com o mesmo back-end do compilador. Os benchmarks que enfatizam fortemente o GC mostrarão que D apresenta um desempenho abismal. Entretanto, tenha certeza de que esse é um problema único (embora grave) de qualidade de implementação, e não uma garantia de lentidão. Além disso, D oferece a capacidade de optar por não participar do GC e ajustar o gerenciamento de memória em bits críticos para o desempenho, enquanto ainda o utiliza nos 95% menos críticos para o desempenho do seu código.
Ultimamente, tenho me esforçado para melhorar o desempenho do GC e os resultados têm sido bastante dramáticos, pelo menos nos benchmarks sintéticos. Esperamos que essas alterações sejam integradas em um dos próximos lançamentos e mitiguem o problema.
fonte
Este é um tópico muito instrutivo, obrigado por todo o trabalho para o OP e os auxiliares.
Uma observação - esse teste não está avaliando a questão geral da penalidade de abstração / recurso ou mesmo a da qualidade de back-end. Ele se concentra em praticamente uma otimização (otimização de loop). Eu acho que é justo dizer que o back-end do gcc é um pouco mais refinado que o do dmd, mas seria um erro supor que a diferença entre eles é tão grande para todas as tarefas.
fonte
Definitivamente, parece um problema de qualidade de implementação.
Fiz alguns testes com o código do OP e fiz algumas alterações. Na verdade, eu consegui o D indo mais rápido para o LDC / clang ++, operando no pressuposto de que as matrizes devem ser alocadas dinamicamente (
xs
e escalares associados). Veja abaixo alguns números.Perguntas para o OP
É intencional que a mesma semente seja usada para cada iteração de C ++, enquanto não é assim para D?
Configuração
Ajustei a fonte D original (apelidada de
scalar.d
) para torná-la portátil entre plataformas. Isso envolveu apenas alterar o tipo dos números usados para acessar e modificar o tamanho das matrizes.Depois disso, fiz as seguintes alterações:
Usado
uninitializedArray
para evitar inits padrão para escalares em xs (provavelmente fez a maior diferença). Isso é importante porque D normalmente insere tudo no padrão, o que o C ++ não faz.Código de impressão fatorado e substituído
writefln
porwriteln
^^
) em vez de multiplicação manual para a etapa final do cálculo da médiasize_type
e substituído adequadamente pelo novoindex_type
alias... resultando em
scalar2.cpp
( pastebin ):Após o teste
scalar2.d
(que priorizou a otimização da velocidade), por curiosidade, substituí os loopsmain
porforeach
equivalentes e o chameiscalar3.d
( pastebin ):Compilei cada um desses testes usando um compilador baseado em LLVM, pois o LDC parece ser a melhor opção para a compilação de D em termos de desempenho. Na minha instalação do x86_64 Arch Linux, usei os seguintes pacotes:
clang 3.6.0-3
ldc 1:0.15.1-4
dtools 2.067.0-2
Eu usei os seguintes comandos para compilar cada um:
clang++ scalar.cpp -o"scalar.cpp.exe" -std=c++11 -O3
rdmd --compiler=ldc2 -O3 -boundscheck=off <sourcefile>
Resultados
Os resultados ( captura de tela da saída bruta do console ) de cada versão da fonte da seguinte maneira:
scalar.cpp
(C ++ original):C ++ define o padrão em 2582 ms .
scalar.d
(fonte OP modificada):Isso foi executado por ~ 2957 ms . Mais lento que a implementação de C ++, mas não muito.
scalar2.d
(alteração do tipo de índice / comprimento e otimização não inicializada da matriz):Em outras palavras, ~ 1860 ms . Até agora, isso está na liderança.
scalar3.d
(foreaches):~ 2182 ms é mais lento que
scalar2.d
, mas mais rápido que na versão C ++.Conclusão
Com as otimizações corretas, a implementação D realmente foi mais rápida que sua implementação equivalente em C ++ usando os compiladores baseados em LLVM disponíveis. A lacuna atual entre D e C ++ para a maioria dos aplicativos parece basear-se apenas nas limitações das implementações atuais.
fonte
O dmd é a implementação de referência da linguagem e, portanto, a maior parte do trabalho é colocada no front-end para corrigir erros, em vez de otimizar o back-end.
"in" é mais rápido no seu caso, porque você está usando matrizes dinâmicas que são tipos de referência. Com ref, você introduz outro nível de indireção (que normalmente é usado para alterar a própria matriz e não apenas o conteúdo).
Os vetores são geralmente implementados com estruturas em que const ref faz todo o sentido. Veja smallptD vs. smallpt para obter um exemplo do mundo real, com cargas de operações vetoriais e aleatoriedade.
Observe que 64 bits também pode fazer a diferença. Uma vez eu perdi isso no x64 gcc compila o código de 64 bits, enquanto o dmd ainda é 32 (o valor mudará quando o codegen de 64 bits amadurecer). Houve uma notável aceleração com "dmd -m64 ...".
fonte
Se C ++ ou D é mais rápido, provavelmente será altamente dependente do que você está fazendo. Eu pensaria que, ao comparar C ++ bem escrito com código D bem escrito, eles geralmente teriam velocidade semelhante ou C ++ seria mais rápido, mas o que o compilador em particular consegue otimizar pode ter um grande efeito, além da linguagem em si.
No entanto, não são poucos os casos em que D tem uma boa chance de bater C ++ para a velocidade. O principal que vem à mente seria o processamento de strings. Graças às capabalidades de fatiamento da matriz de D, seqüências de caracteres (e matrizes em geral) podem ser processadas muito mais rapidamente do que você pode fazer facilmente em C ++. Para o D1, o processador XML do Tango é extremamente rápido , graças principalmente aos recursos de corte de matriz do D (e, esperançosamente, o D2 terá um analisador de XML igualmente rápido depois que o que estiver sendo trabalhado no Phobos for concluído). Portanto, se o D ou C ++ será mais rápido, dependerá muito do que você está fazendo.
Agora eu estou surpreso que você esteja vendo uma diferença de velocidade nesse caso específico, mas é o tipo de coisa que eu esperaria melhorar à medida que o dmd melhorar. O uso do gdc pode produzir melhores resultados e provavelmente seria uma comparação mais próxima do próprio idioma (e não do back-end), uma vez que é baseado no gcc. Mas não me surpreenderia se houvesse várias coisas que poderiam ser feitas para acelerar o código que o dmd gera. Eu não acho que haja muita dúvida de que o gcc é mais maduro que o dmd neste momento. E as otimizações de código são um dos principais frutos da maturidade do código.
Por fim, o que importa é o desempenho do dmd para seu aplicativo em particular, mas concordo que seria definitivamente bom saber o quão C ++ e D se comparam em geral. Em teoria, eles devem ser praticamente iguais, mas realmente depende da implementação. Penso que seria necessário um conjunto abrangente de parâmetros para testar realmente como os dois atualmente se comparam.
fonte
Você pode escrever o código C é D, na medida em que for mais rápido, isso dependerá de muitas coisas:
As diferenças no primeiro não são justas para se arrastar. O segundo pode dar ao C ++ uma vantagem, pois, se houver, possui menos recursos pesados. O terceiro é o mais divertido: o código D, de certa forma, é mais fácil de otimizar, porque em geral é mais fácil de entender. Também tem a capacidade de executar um alto grau de programação generativa, permitindo que coisas como código detalhado e repetitivo, mas rápido, sejam escritas de forma mais curta.
fonte
Parece um problema de qualidade de implementação. Por exemplo, aqui está o que eu tenho testado:
Com
ManualInline
definido, recebo 28 segundos, mas sem recebo 32. Portanto, o compilador nem sequer está embutindo essa função simples, o que acho claro que deveria estar.(Minha linha de comando é
dmd -O -noboundscheck -inline -release ...
.)fonte