D의 일부 기능이 마음에 들지만 런타임 패널티가 있다면 관심이 있습니까?
비교하기 위해 C ++과 D에서 많은 짧은 벡터의 스칼라 곱을 계산하는 간단한 프로그램을 구현했습니다. 결과는 놀랍습니다.
- D : 18.9 초 [최종 런타임은 아래 참조]
- C ++ : 3.8 초
C ++이 실제로 거의 5 배나 빠르거나 D 프로그램에서 실수를 했습니까?
나는 최근 리눅스 데스크탑에서 g ++ -O3 (gcc-snapshot 2011-02-19) 및 Dmd -O (dmd 2.052)로 C ++를 컴파일했습니다. 결과는 여러 번의 실행에서 재현 가능하고 표준 편차는 무시할 수 있습니다.
여기에 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;
}
그리고 여기 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;
}
답변
모든 최적화를 활성화하고 모든 안전 점검을 비활성화하려면 다음 DMD 플래그를 사용하여 D 프로그램을 컴파일하십시오.
-O -inline -release -noboundscheck
편집 : g ++, dmd 및 gdc로 프로그램을 시도했습니다. dmd는 뒤쳐 지지만 gdc는 g ++에 매우 가까운 성능을 달성합니다. 내가 사용한 명령 줄은 gdmd -O -release -inline
gdmd가 ddc 옵션을 허용하는 gdc를 감싸는 래퍼입니다.
어셈블러 목록을 보면 dmd 나 gdc inlined처럼 보이지 scalar_product
않지만 g ++ / gdc는 MMX 명령을 내렸 으므로 루프를 자동 벡터화 할 수 있습니다.
답변
D 속도를 늦추는 것 중 하나는 하위 파 가비지 수집 구현입니다. GC에 큰 부담을주지 않는 벤치 마크는 동일한 컴파일러 백엔드로 컴파일 된 C 및 C ++ 코드와 매우 유사한 성능을 보여줍니다. GC에 큰 스트레스를주는 벤치 마크는 D가 심하게 작음을 보여줍니다. 그러나 이는 느리게 구현 된 보장이 아니라 단일 (심각한) 구현 품질 문제입니다. 또한 D를 사용하면 성능이 중요하지 않은 코드에서 GC를 선택 해제하고 메모리 관리를 조정하여 성능이 중요하지 않은 코드의 95 %에서 메모리를 계속 사용할 수 있습니다.
나는 한 최근 GC 성능을 향상에 어떤 노력을 하고 그 결과 적어도 합성 벤치 마크에서, 오히려 극적이었다. 이러한 변경 사항이 다음 몇 가지 릴리스 중 하나에 통합되어 문제가 완화되기를 바랍니다.
답변
OP와 도우미의 모든 작업에 감사드립니다.
한 가지 참고 사항-이 테스트는 추상화 / 기능 페널티 또는 백엔드 품질에 대한 일반적인 질문을 평가하지 않습니다. 사실상 하나의 최적화 (루프 최적화)에 중점을 둡니다. 나는 gcc의 백엔드가 dmd의 백엔드보다 약간 더 세련되었다고 말하는 것이 공평하다고 생각하지만, 그들 사이의 간격이 모든 작업에 대해 크다고 가정하는 것은 실수 일 것입니다.
답변
분명히 구현 품질 문제처럼 보입니다.
OP 코드로 몇 가지 테스트를 실행하고 약간 변경했습니다. 실제로 배열을 동적으로 할당 해야 한다는 가정 ( xs
및 관련 스칼라)을 사용 하여 LDC / clang ++에 대해 D가 더 빨라졌습니다 . 일부 숫자는 아래를 참조하십시오.
OP에 대한 질문
C에 대한 반복마다 동일한 시드를 사용하고 D에는 그렇지 않은 것이 의도적 인 것입니까?
설정
scalar.d
플랫폼간에 이식성을 갖도록 원본 D 소스 ( )를 조정했습니다 . 여기에는 배열의 크기에 액세스하고 수정하는 데 사용되는 숫자 유형 만 변경하는 것이 포함되었습니다.
그 후에 다음과 같이 변경했습니다.
-
uninitializedArray
xs의 스칼라에 대한 기본 초기화를 피하는 데 사용 됩니다 (아마도 가장 큰 차이를 만들었 음). D는 일반적으로 모든 것을 자동으로 초기화하지만 C ++에서는 그렇지 않기 때문에 중요합니다. -
코드를 인쇄에서 고려 교체
writefln
와 함께writeln
- 선택적 가져 오기로 변경
^^
평균 계산의 마지막 단계에 수동 곱셈 대신 pow 연산자 ( )를 사용했습니다.- 를 제거하고
size_type
새index_type
별칭으로 적절하게 교체
… 따라서 scalar2.cpp
( pastebin )이 발생합니다.
import std.stdio : writeln;
import std.datetime : Clock, Duration;
import std.array : uninitializedArray;
import std.random : uniform;
alias result_type = long;
alias value_type = int;
alias vector_t = value_type[];
alias index_type = typeof(vector_t.init.length);// Make index integrals portable - Linux is ulong, Win8.1 is uint
immutable long N = 20000;
immutable int size = 10;
// Replaced for loops with appropriate foreach versions
value_type scalar_product(in ref vector_t x, in ref vector_t y) { // "in" is the same as "const" here
value_type res = 0;
for(index_type i = 0; i < size; ++i)
res += x[i] * y[i];
return res;
}
int main() {
auto tm_before = Clock.currTime;
auto countElapsed(in string taskName) { // Factor out printing code
writeln(taskName, ": ", Clock.currTime - tm_before);
tm_before = Clock.currTime;
}
// 1. allocate and fill randomly many short vectors
vector_t[] xs = uninitializedArray!(vector_t[])(N);// Avoid default inits of inner arrays
for(index_type i = 0; i < N; ++i)
xs[i] = uninitializedArray!(vector_t)(size);// Avoid more default inits of values
countElapsed("allocation");
for(index_type i = 0; i < N; ++i)
for(index_type j = 0; j < size; ++j)
xs[i][j] = uniform(-1000, 1000);
countElapsed("random");
// 2. compute all pairwise scalar products:
result_type avg = 0;
for(index_type i = 0; i < N; ++i)
for(index_type j = 0; j < N; ++j)
avg += scalar_product(xs[i], xs[j]);
avg /= N ^^ 2;// Replace manual multiplication with pow operator
writeln("result: ", avg);
countElapsed("scalar products");
return 0;
}
테스트 후 scalar2.d
(속도 최적화를 우선 순위로 한) 호기심으로 루프 main
를 foreach
동등한 것으로 대체하고 scalar3.d
( pastbin ) 이라고했습니다 .
import std.stdio : writeln;
import std.datetime : Clock, Duration;
import std.array : uninitializedArray;
import std.random : uniform;
alias result_type = long;
alias value_type = int;
alias vector_t = value_type[];
alias index_type = typeof(vector_t.init.length);// Make index integrals portable - Linux is ulong, Win8.1 is uint
immutable long N = 20000;
immutable int size = 10;
// Replaced for loops with appropriate foreach versions
value_type scalar_product(in ref vector_t x, in ref vector_t y) { // "in" is the same as "const" here
value_type res = 0;
for(index_type i = 0; i < size; ++i)
res += x[i] * y[i];
return res;
}
int main() {
auto tm_before = Clock.currTime;
auto countElapsed(in string taskName) { // Factor out printing code
writeln(taskName, ": ", Clock.currTime - tm_before);
tm_before = Clock.currTime;
}
// 1. allocate and fill randomly many short vectors
vector_t[] xs = uninitializedArray!(vector_t[])(N);// Avoid default inits of inner arrays
foreach(ref x; xs)
x = uninitializedArray!(vector_t)(size);// Avoid more default inits of values
countElapsed("allocation");
foreach(ref x; xs)
foreach(ref val; x)
val = uniform(-1000, 1000);
countElapsed("random");
// 2. compute all pairwise scalar products:
result_type avg = 0;
foreach(const ref x; xs)
foreach(const ref y; xs)
avg += scalar_product(x, y);
avg /= N ^^ 2;// Replace manual multiplication with pow operator
writeln("result: ", avg);
countElapsed("scalar products");
return 0;
}
LDC가 성능 측면에서 D 컴파일에 가장 적합한 옵션 인 것처럼 보이기 때문에 LLVM 기반 컴파일러를 사용하여 이러한 각 테스트를 컴파일했습니다. x86_64 Arch Linux 설치에서 다음 패키지를 사용했습니다.
clang 3.6.0-3
ldc 1:0.15.1-4
dtools 2.067.0-2
다음 명령을 사용하여 각각을 컴파일했습니다.
- C ++ :
clang++ scalar.cpp -o"scalar.cpp.exe" -std=c++11 -O3
- 디:
rdmd --compiler=ldc2 -O3 -boundscheck=off <sourcefile>
결과
각 소스 버전의 결과 ( 원시 콘솔 출력 스크린 샷 )는 다음과 같습니다.
-
scalar.cpp
(원래 C ++) :allocation: 2 ms random generation: 12 ms result: 29248300000 time: 2582 ms
C ++는 표준을 2582ms로 설정합니다 .
-
scalar.d
(수정 된 OP 소스) :allocation: 5 ms, 293 μs, and 5 hnsecs random: 10 ms, 866 μs, and 4 hnsecs result: 53237080000 scalar products: 2 secs, 956 ms, 513 μs, and 7 hnsecs
이것은 ~ 2957 ms 동안 실행되었습니다 . C ++ 구현보다 느리지 만 너무 많지는 않습니다.
-
scalar2.d
(인덱스 / 길이 유형 변경 및 초기화되지 않은 배열 최적화) :allocation: 2 ms, 464 μs, and 2 hnsecs random: 5 ms, 792 μs, and 6 hnsecs result: 59 scalar products: 1 sec, 859 ms, 942 μs, and 9 hnsecs
다시 말해, ~ 1860 ms 입니다. 지금까지 이것은 주도하고 있습니다.
-
scalar3.d
(두려움) :allocation: 2 ms, 911 μs, and 3 hnsecs random: 7 ms, 567 μs, and 8 hnsecs result: 189 scalar products: 2 secs, 182 ms, and 366 μs
~ 2182ms 는 ~ 보다 느리지
scalar2.d
만 C ++ 버전보다 빠릅니다.
결론
올바른 최적화를 통해 D 구현은 실제로 사용 가능한 LLVM 기반 컴파일러를 사용하는 동등한 C ++ 구현보다 빠릅니다. 대부분의 응용 프로그램에서 D와 C ++ 사이의 현재 격차는 현재 구현의 한계에 기반한 것으로 보입니다.
답변
dmd는 언어의 참조 구현이므로 대부분의 작업은 백엔드를 최적화하는 대신 버그를 수정하기 위해 프론트 엔드에 배치됩니다.
“in”이 더 빠를 경우 참조 유형 인 동적 배열을 사용하게됩니다. ref를 사용하면 다른 수준의 간접 지시를 도입합니다 (일반적으로 내용뿐만 아니라 배열 자체를 변경하는 데 사용됨).
벡터는 일반적으로 const ref가 완벽하게 이해되는 구조체로 구현됩니다. 많은 벡터 연산과 임의성을 특징으로하는 실제 예제는 smallptD 와 smallpt 를 참조하십시오 .
64 비트도 차이를 만들 수 있습니다. x64에서 gcc는 64 비트 코드를 컴파일하는 반면 dmd는 여전히 32로 기본 설정되어 있습니다 (64 비트 codegen이 성숙 할 때 변경됨). “dmd -m64 …”로 놀라운 속도 향상이있었습니다.
답변
C ++ 또는 D가 더 빠른지 여부는 수행중인 작업에 따라 크게 달라질 수 있습니다. 잘 작성된 C ++을 잘 작성된 D 코드와 비교할 때 일반적으로 비슷한 속도이거나 C ++이 빠를 것이지만 특정 컴파일러가 최적화하도록 관리하는 것은 언어와는 별도로 큰 영향을 줄 수 있다고 생각합니다. 그 자체.
그러나이 있다 D는 속도 ++ C 박동의 좋은 기회를 의미합니다 몇 가지 경우가. 가장 중요한 것은 문자열 처리입니다. D의 배열 슬라이싱 기능 덕분에 문자열 (및 일반적으로 배열)은 C ++에서보다 훨씬 빠르게 처리 할 수 있습니다. D1의 경우 Tango의 XML 프로세서는 주로 D의 어레이 슬라이싱 기능 덕분에 매우 빠릅니다 (D2는 현재 Phobos에서 작업하고있는 XML 구문 분석기가 완료되면 유사하게 빠른 XML 파서를 갖기를 바랍니다). 따라서 궁극적으로 D 또는 C ++가 더 빨라질 지 여부는 수행중인 작업에 따라 크게 달라집니다.
지금, 나는 하고 이 특정 경우에 속도가 이러한 차이를보고있는 것을 놀라게하지만 내가 DMD가 향상으로 개선 될 전망 것이라고 물건의 일종이다. gdc를 사용하면 더 나은 결과를 얻을 수 있으며 gcc 기반이기 때문에 백엔드가 아닌 언어 자체를 더 자세히 비교할 수 있습니다. 그러나 dmd가 생성하는 코드의 속도를 높이기 위해 할 수있는 일이 많이 있다면 놀라지 않을 것입니다. 나는이 시점에서 gcc가 dmd보다 성숙하다는 질문이 많이 없다고 생각합니다. 그리고 코드 최적화는 코드 성숙의 주요 결과 중 하나입니다.
궁극적으로 중요한 것은 특정 응용 프로그램에서 dmd가 얼마나 잘 수행되는지이지만 C ++과 D가 일반적으로 얼마나 잘 비교되는지 아는 것이 분명하다는 데 동의합니다. 이론적으로는 거의 동일해야하지만 실제로 구현에 달려 있습니다. 그러나 현재 두 제품이 얼마나 잘 비교되는지 테스트하려면 종합적인 벤치 마크 세트가 필요하다고 생각합니다.
답변
C 코드를 D로 작성할 수 있습니다. 빠를수록 많은 것들에 달려 있습니다.
- 사용하는 컴파일러
- 사용하는 기능
- 얼마나 적극적으로 최적화
첫 번째 차이점은 불공평합니다. 두 번째 차이점은 C ++에 무거운 기능이 적기 때문에 이점이 있습니다. 세 번째는 재미있는 것입니다. 일반적으로 이해하기 쉽기 때문에 어떤 방식으로 D 코드를 최적화하는 것이 더 쉽습니다. 또한 장황하고 반복적이지만 빠른 코드와 같은 것들을 더 짧은 형태로 작성할 수 있도록 대규모 생성 프로그램을 수행 할 수 있습니다.