Мне нравятся некоторые функции D, но было бы интересно, если они будут иметь штраф за время выполнения?
Для сравнения я реализовал простую программу, которая вычисляет скалярные произведения многих коротких векторов как на C ++, так и на D. Результат удивительный:
- D: 18,9 с [окончательное время выполнения см. Ниже]
- C ++: 3,8 с
C ++ действительно почти в пять раз быстрее или я ошибся в программе D?
Я скомпилировал C ++ с g ++ -O3 (gcc-snapshot 2011-02-19) и D с dmd -O (dmd 2.052) на умеренном недавнем рабочем столе Linux. Результаты воспроизводимы в нескольких сериях, а стандартные отклонения незначительны.
Вот программа на 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;
}
c++
performance
runtime
d
Lars
источник
источник
avg = avg / N*N
(порядок операций).dmd ... trace.def
что получаюerror: unrecognized file extension def
. А в dmd docs для optlink упоминается только Windows.Ответы:
Чтобы включить все оптимизации и отключить все проверки безопасности, скомпилируйте вашу программу D со следующими флагами DMD:
РЕДАКТИРОВАТЬ : Я пробовал ваши программы с g ++, dmd и gdc. dmd действительно отстает, но gdc по производительности очень близок к g ++. Я использовал
gdmd -O -release -inline
командную строку (gdmd - это оболочка для gdc, которая принимает параметры dmd).Глядя на листинг ассемблера, похоже, что ни dmd, ни gdc не встроены
scalar_product
, но g ++ / gdc действительно испускал инструкции MMX, поэтому они могли автоматически векторизовать цикл.источник
Одна большая вещь, которая замедляет работу D, - это некачественная реализация сборки мусора. Тесты, которые не сильно нагружают сборщик мусора, покажут производительность, очень схожую с производительностью кода C и C ++, скомпилированного с использованием того же внутреннего модуля компилятора. Тесты, которые сильно нагружают GC, покажут, что D работает ужасно. Но будьте уверены, что это единственная (хотя и серьезная) проблема качества реализации, а не гарантия медлительности. Кроме того, D дает вам возможность отказаться от GC и настроить управление памятью в критических для производительности битах, при этом используя его в менее критичных для производительности 95% вашего кода.
В последнее время я приложил некоторые усилия для улучшения производительности GC, и результаты были довольно впечатляющими, по крайней мере, на синтетических тестах. Надеюсь, эти изменения будут внесены в один из следующих нескольких выпусков и устранят проблему.
источник
Это очень поучительная ветка, спасибо за всю работу OP и помощникам.
Одно замечание - этот тест не оценивает общий вопрос о штрафах за абстракцию / функциональность или даже о качестве серверной части. Он ориентирован практически на одну оптимизацию (оптимизация цикла). Я думаю, будет справедливо сказать, что бэкэнд gcc несколько более усовершенствован, чем dmd, но было бы ошибкой предполагать, что разрыв между ними столь же велик для всех задач.
источник
Определенно кажется проблемой качества реализации.
Я провел несколько тестов с кодом OP и внес некоторые изменения. Я фактически заставил D работать быстрее для LDC / clang ++, работая в предположении, что массивы должны распределяться динамически (
xs
и связанные скаляры). Ниже приведены некоторые цифры.Вопросы к ОП
Является ли намеренно использование одного и того же начального числа для каждой итерации C ++, а не для D?
Настроить
Я изменил исходный код D (дублированный
scalar.d
), чтобы сделать его переносимым между платформами. Это включало только изменение типа чисел, используемых для доступа к массивам и изменения их размера.После этого я внес следующие изменения:
Используется,
uninitializedArray
чтобы избежать инициализации по умолчанию для скаляров в xs (вероятно, имело наибольшее значение). Это важно, потому что D обычно по умолчанию все инициализирует молча, чего не делает C ++.Код печати исключен и заменен
writefln
наwriteln
^^
) вместо ручного умножения на последнем этапе вычисления среднегоsize_type
и заменен соответствующим образом новымindex_type
псевдонимом... что приводит к
scalar2.cpp
( pastebin ):После тестирования
scalar2.d
(который по приоритетам оптимизации скорости), из любопытства я заменил петлю вmain
сforeach
эквивалентами, и назвал егоscalar3.d
( Pastebin ):Я скомпилировал каждый из этих тестов с использованием компилятора на основе LLVM, поскольку LDC кажется лучшим вариантом для компиляции D с точки зрения производительности. В моей установке x86_64 Arch Linux я использовал следующие пакеты:
clang 3.6.0-3
ldc 1:0.15.1-4
dtools 2.067.0-2
Я использовал следующие команды для компиляции каждого:
clang++ scalar.cpp -o"scalar.cpp.exe" -std=c++11 -O3
rdmd --compiler=ldc2 -O3 -boundscheck=off <sourcefile>
Полученные результаты
Результаты ( снимок экрана необработанного вывода консоли ) для каждой версии исходного кода выглядят следующим образом:
scalar.cpp
(оригинальный C ++):C ++ устанавливает стандарт на 2582 мс .
scalar.d
(модифицированный источник OP):Это длилось ~ 2957 мс . Медленнее, чем реализация на C ++, но не намного.
scalar2.d
(изменение типа индекса / длины и оптимизация uninitializedArray):Другими словами, ~ 1860 мс . Пока это лидирует.
scalar3.d
(foreaches):~ 2182 мс медленнее
scalar2.d
, но быстрее, чем версия C ++.Вывод
При правильной оптимизации реализация D фактически пошла быстрее, чем ее эквивалентная реализация на C ++ с использованием доступных компиляторов на основе LLVM. Текущий разрыв между D и C ++ для большинства приложений, по-видимому, основан только на ограничениях текущих реализаций.
источник
dmd - это эталонная реализация языка, и поэтому большая часть работы направлена на исправление ошибок, а не на оптимизацию серверной части.
"in" в вашем случае быстрее, потому что вы используете динамические массивы, которые являются ссылочными типами. С помощью ref вы вводите другой уровень косвенности (который обычно используется для изменения самого массива, а не только его содержимого).
Векторы обычно реализуются с помощью структур, в которых const ref имеет смысл. См. « SmallptD против smallpt» для получения реального примера с множеством векторных операций и случайности.
Обратите внимание, что 64-битная версия также может иметь значение. Однажды я пропустил, что на x64 gcc компилирует 64-битный код, в то время как dmd по-прежнему имеет значение 32 (изменится, когда 64-битный кодогенератор созреет). Было замечательно ускорение с "dmd -m64 ...".
источник
Будет ли C ++ или D быстрее, скорее всего, будет сильно зависеть от того, что вы делаете. Я бы подумал, что при сравнении хорошо написанного C ++ с хорошо написанным кодом D они, как правило, будут иметь одинаковую скорость, или C ++ будет быстрее, но то, что конкретный компилятор сможет оптимизировать, может иметь большой эффект, кроме языка сам.
Тем не менее, есть несколько случаев , когда D есть хорошие шансы в избиении C ++ для скорости. Главное, что приходит в голову, - это обработка строк. Благодаря возможностям нарезки массивов в D строки (и массивы в целом) могут обрабатываться намного быстрее, чем это можно сделать в C ++. Для D1 XML-процессор Tango работает чрезвычайно быстро , в первую очередь благодаря возможностям нарезки массива D (и, надеюсь, у D2 будет такой же быстрый XML-анализатор, как только будет завершен тот, над которым в настоящее время работают для Phobos). Итак, в конечном итоге, будет ли D или C ++ работать быстрее, будет очень зависеть от того, что вы делаете.
Теперь я буду удивлён , что вы видите такую разницу в скорости в данном конкретном случае, но это такое дело , что я бы ожидать улучшения , как DMD улучшается. Использование gdc может дать лучшие результаты и, вероятно, будет более близким сравнением самого языка (а не бэкэнда), учитывая, что он основан на gcc. Но меня совсем не удивит, если есть несколько вещей, которые можно сделать для ускорения кода, генерируемого dmd. Я не думаю, что есть много сомнений в том, что на данный момент gcc более зрелый, чем dmd. А оптимизация кода - один из главных плодов зрелости кода.
В конечном счете, важно то, насколько хорошо dmd работает для вашего конкретного приложения, но я согласен с тем, что было бы определенно неплохо узнать, насколько хорошо C ++ и D сравниваются в целом. Теоретически они должны быть примерно одинаковыми, но на самом деле это зависит от реализации. Я думаю, что потребуется исчерпывающий набор тестов, чтобы действительно проверить, насколько хорошо они сравниваются в настоящее время.
источник
Вы можете написать код C - это D, насколько он быстрее, это будет зависеть от многих вещей:
Различия в первом не справедливо перетаскивать. Второй может дать C ++ преимущество, поскольку он, во всяком случае, имеет меньше тяжелых функций. Третий - интересный: код в некотором смысле легче оптимизировать, потому что в целом его легче понять. Также он имеет возможность выполнять большую часть генеративного программирования, позволяя писать такие вещи, как подробный и повторяющийся, но быстрый код, в более коротких формах.
источник
Похоже на проблему качества реализации. Например, вот что я тестировал:
С
ManualInline
определением я получаю 28 секунд, но без 32. Таким образом, компилятор даже не встраивает эту простую функцию, что, как мне кажется, должно быть очевидно.(Моя командная строка
dmd -O -noboundscheck -inline -release ...
.)источник