Определенно кажется проблемой качества реализации.
Я провел несколько тестов с кодом OP и внес некоторые изменения. Я на самом деле заставил D работать быстрее для LDC / clang ++, работая в предположении, что массивы должны выделяться динамически (xs
и связанные скаляры). Ниже приведены некоторые цифры.
Вопросы для ОП
Намеренно ли использовать одно и то же начальное число для каждой итерации C ++, но не для D?
Настройка
Я настроил исходный D-источник (дублированный scalar.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
( 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
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;
}
Я скомпилировал каждый из этих тестов, используя компилятор на основе LLVM, поскольку LDC представляется наилучшим вариантом для D-компиляции с точки зрения производительности. В моей установке 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
- D:
rdmd --compiler=ldc2 -O3 -boundscheck=off <sourcefile>
Результаты
Результаты ( снимок экрана с необработанным выводом консоли ) для каждой версии источника выглядят следующим образом:
scalar.cpp
(оригинал C ++):
allocation: 2 ms
random generation: 12 ms
result: 29248300000
time: 2582 ms
C ++ устанавливает стандарт на 2582 мс .
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 мс . Медленнее, чем реализация 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 мс . Пока это лидирует.
scalar3.d
(foreaches):
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
~ 2182 мс медленнее, чем scalar2.d
, но быстрее, чем версия C ++.
Заключение
При правильной оптимизации реализация D фактически шла быстрее, чем ее эквивалентная реализация C ++ с использованием доступных компиляторов на основе LLVM. Кажется, что нынешний разрыв между D и C ++ для большинства приложений основан только на ограничениях текущих реализаций.