Pull to refresh

Comments 63

Справа подписано. Синяя — C#, коричневая C# + Parr.Ext, зеленая C++, фиолетовая C++ & OpenMP.
Ага, спасибо.
На 1280*800 правый край картинки съедается версткой хабра.
верстка хабра нормальная, просто картинки надо чуть поменьше делать.
Я не критикую верстку — я констатирую факт.
Это моя вина — монитор 1680х1050 расслабляет.
инетерсно было бы увидеть, конечно, другие языки типа Java/Scala и Erlang?
Все зависит от того какие приложения конкретно писать. Например, алгоритмы шифрования (в некотором одном конкретно взятом проекте) работают раза в 1.5-2 медленнее чем native code.
Perfomance-critical, конечно, не стоит. Только вряд ли вся программа критична к скорости, можно вынести модуль на Си++
Согласен. По возможности именно так и поступаю.
Имхо какой-то странный график. Полсекунды на умножение матрицы 2x2 у C# + Parr.Ext. Очевидно, че-то не так )))
Это как раз нормально. У системы есть некий overhead, который нужно хотя бы один раз подгрузить.
Так я как раз о том, что надо этот 1 раз в график не включать.
Надо включать для чистоты эксперимента. Преимуществ не видно тогда.
Преимуществ не видно? Внимательней посмотрите на график. Подписано же, что шкала логарифмическая.
На матрице 1024 * 1024 результат C++ & OpenMp меньше 4х секунд, а Plain C# — 64 секунды. А то, о чем Вы говорите — это не тест скорости алгоритма, а тест инициализации компиляции / RTTI.
«Неправильно ты, дядя Федор»… Перед фактическим замером времени первый вызов функции умножения во всех случаях надо было сделать вхолостую — в случае C#, например, происходит загрузка сборок (если необходимо) и JIT-компиляция, в случае C++ тоже что-то такое низкоуровневое («#pragma omp parallel… „) да происходит.
Именно это и делается с матрицами 2х2. Но следует помнить что если у клиента этот алгоритм должен с первого раза производить «правильное впечатление», то брать нужно именно эти данные, а не те где алгоритм «подготовлен».
Может тогда сделаете ещё один график без учета инициализации? Очень интересно сравнить производительность.
Как правило алгоритм исполняется большое количество раз, а подготовка одна. Для времени подготовки надо делать отдельный тест, а для алгоритма — отдельный.
Писать или не писать — довольно сложный вопрос. Контекст задачи решает все. Вариантов решения много. Если критична поддержка высокопроизводительного приложения, изменения по ходу проекта и прочие приблуды, то, наверное, стоит. Если приложение пишется раз и надолго (а таких не бывает), то скорее всего не стоит. По моим сравнениям (писал алгоритм расчета коэффициента отражения волны от многослойных структур) .Net вполне пригоден для расчетов и С++ он проигрывает минимально, а поддерживать его удобнее. Естественно переносимость .Net-приложения много лучше.
Но, возможно, это лишь частный случай.
Про переносимость приложения — сомнительно мне.
Самое переносимое, что только можно придумать — С, для ввода вывода использующий стандартную библиотеку.
Под переносимостью я не имел ввиду кроссплатформенность.
С OMP не знаком, но строка omp_set_num_threads(2); смущает.
Зависит от кол-ва ядер.
Мне кажется, заголовок неправильный. Надо было писать производительность разных языков на вычислении матричных операций. Но реальные приложения как правило, не только матрицы вычисляют))
Лучше бы взяди простейшие приложения вроде блокнота, и на нем померяли (подозреваю, что Windows Notepad тут был бы самым быстрым).
UFO just landed and posted this here
Время запуска, скорость обработки поступающих символов (симв/с), скорость поиска, и т.д — выбирайте любой показатель.
Думаю, для пользователя главный показатель в данном примере — время запуска, которе должно быть настолько малым, что не воспринимается человеком. Думаю, так
А какой смысл «измерять блокнот», если автором ставилась задача определить язык для performance-critical приложений?
Конечно в реальных приложениях все сложнее. Но этот пример приводит Майкрософт, вот я и протестировал его чтобы понять «как все на самом деле». А умножение больших матриц — это не надуманная вещь, в инжинерных приложениях сплошь и рядом.
Понятно, я как-то больше за обычных пользователей переживал))
Обычные пользователи знают это дело под кодовым названием «физика». В играх.
Надеюсь, вы не предлагаете просчет физики в играх делать на C#?
а это точно имеет отношения к C#, как к языку? может это относится чисто к.НЕТ платформе?
Вполне возможно! System.Array один на всех, насколько я понимаю.
Приоткрою завесу и объясню результаты.

Господа, этот тест необъективен.

Во-первых, я так и не увидел какой компилятор использовался и с какими опциями компилировался исходник на С++.

Во-вторых, давайте посмотрим как автор хранит массивы в C#:

double[,] m1 = new double[size, size];
double[,] m2 = new double[size, size];
double[,] result = new double[size, size];


В памяти такой массив будет идти подряд а адресоваться как (i*rows + j).

Теперь глянем на вариант С++:

double ** mi1 = new double *[sizes[i]];
double ** mi2 = new double *[sizes[i]];
double ** result = new double*[sizes[i]];
for (int j = 0; j < sizes[i]; ++j) {
mi1[j] = new double [sizes[i]];
mi2[j] = new double [sizes[i]];
result[j] = new double [sizes[i]];


Т.е. у нас есть массив указателей, который указывает на массив double'ов, которые выделены в разных участках памяти. :)

А теперь посмотрите на процедуру умножения:

result[i][j] += m1[i][k] * m2[k][j];
<\em>

Мы скачем по разным кускам памяти. Т.е. кеш процессора не работает ВООБЩЕ! Либо эффективность его работы минимальна. Для человека думаешго о производительности это должно быть совершенно очевидно.

Умножение матрицы 1024х1024 занимает 60 секунд в реализации на С++? Только если у кого-то руки, пардон, не оттуда растут.
В моей реализации умножение матриц 1024х1024 занимает 2 секунды. Никакого распараллеливания. Процессор AMD 2.2 ГЦ, компилятор MSVC++ 8.1
Если кто не верит могу выложить реализацию.
Т.е. речь о том, что мы измеряем не производительность кода, а большую часть времени ждём пока данные придут из памяти.
Нет, это на C# — на C++ значительно меньше.
Если заюзать CUDA на приличном GPU то такое умножение займёт мару миллисекунд. И всё это доступно на обычных настольных ПК(у меня на ноуте 9600GS с 32 ядрами такое умножение занимает 68 ms).

Так же можно посмотреть алгоритмы Грановского, Штрассена, Виноградова. Они отлично подходят для распараллеливания.
У меня на ноуте нет 9600, увы. А вообще конечно будущее за CUDA и подобными, с этим не поспоришь. Только пока насколько я знаю графический карты работают только с float, а double им приходится эмулировать. Или я не прав?
Смотря какие карты. У NVIDIA в отношении CUDA есть такой параметр «Compute Capability», на данный момент существует несколько версий: 1.0, 1.1, 1.3

К версии 1.3 относятся Tesla и карты семейства GTX 2хх. В этих картах есть полная поддержка double. В остальных работа с double осуществляется как с двумя float и, кстати, порядком тормозит весь процесс.
Все Directx10 карты (nvidia 8-й серии и выше, например) поддерживают работу с целыми числами.
Причём здесь DirectX10 и работа с целыми числами?
Ну как причём? Нативная поддержка операций над целыми числами (в т.ч. bitwise операций) пришла в мейнстрим только с выходом DX10, а точнее Shader Model 4.
Ааа, внимательно прочитал комментарий. Мне почему-то показалось что имелось в виду поддержку int-ов приходится эмулировать, а не double-ов.
Он хотел сказать что на графике 60 сек. это у C#. А у C++ между 8 и 16. Там шкала Y растет экпонентциально.
На мой взгляд на .Net нужно писать, если критична безопасность, на C++ можно ненакром положить не только свою программу, но и всю систему, какому пользователю такое понравиться, на его месте я бы подождал лишних пару секунд, все ведь понимают, что если алгоритм хорош, то скорее всего он будет таковым и на другом языке, и ещё время разработки гараздо меньше и поддержка проще
Всю систему положить userspace программа (к тому же как правило выполняющаяся с правами обычного пользователя) по идее не может. правда, в существующих сегодня распространенных ОС — все-таки может((
Неправы вы. На C++ положить всю систему с помощью userspace программы так же сложно на практическом уровне, как и на C#. Феньки про managed/unmanaged к другим вещам относятся — защита от переполнений буфера и подобное.
Ненароком положить систему тяжело на C++. Это нужно какие-нибудь трюки с GPU вытворять, а не матрицы перемножать…
Ну, например ненароком(!) засрать всю память. В C# за тобой уберёт GC.
Использование smart pointers и object trees(имею ввиду реализацию из Qt) в C++ позволяет в большинстве случаев забыть про управление памятью.

Но то, что засрать можно согласен. А GC вряд ли сообразит, если я в цикле начну выделять память тоннами.
Для этого есть ограничение памяти на процесс) и автоубийство процеса, слишком интенсивно расходующего ресурсы.
Не хватает анализа времен при размерах меньше 24, что самое интересное.

Судя по всему, сказывается меньший working set С++ по памяти, из-за чего он и выигрывает.
Другие языки ведут себя непонятным образом из-за (по большому счету) не детерминированности распределения памяти и большего cache miss rate.
C++ & OpenMP тормозит из-за дороговизны запуска новых тредов.
Вот интересное обсуждение на StackOverflow.

Мое личное мнение: разница в производительности между C# и C++ не принципиальная (даже мизерная), но у C# (.NET) большое преимущество в плане скорости разработки, удобства поддержки проектов и надежности. Но все это дело вкуса, конечно.
К сожалению, в данном конкретном случае результаты малоинтересны.
Как уже заметили выше, кусок на C++ написан плохо.
А можно «правильный пример» в студию? :)
Правильный пример придумать сложно. Навскидку придумывается ещё 3 варианта (6 если с OpenMP).
Обзову исходный вариант вариантом 0.

1. Хранить непрерывный кусок памяти. Тогда внутренний цикл:
double res = 0;
for (int k = 0; k < size; k++)
{
res += m1[i*size+k] * m2[k*size+j];
}
result[i*size+j] = res;

2. Хранить непрерывный кусок памяти. Перед перемножением делать inplace (или outplace)
транспонирование второй матрицы, если inplace, то повторно транспонируем перед выходом.
Тогда внутренний цикл:

double res = 0;
for (int k = 0; k < size; k++)
{
res += m1[i*size+k] * m2[j*size+k];
}
result[i*size+j] = res;

3. Помесь 0 и 2. Хранить также как в 0, перестранспонировать перед перемножением.

Результаты моего тестирования.
size 0 1 2 2+OMP
2 6.78E-07 1.52E-06 1.66E-06 0.00192453
3 9.10E-07 1.17E-06 1.62E-06 0.000102599
4 9.88E-07 1.31E-06 1.61E-06 1.94E-05
6 2.39E-06 2.46E-06 2.98E-06 1.93E-05
8 3.32E-06 3.72E-06 4.72E-06 0.00010015
12 9.30E-06 8.63E-06 8.73E-06 0.000108805
16 1.90E-05 1.72E-05 1.59E-05 0.000129605
24 4.21E-05 5.10E-05 4.39E-05 4.84E-05
32 0.000105829 0.000110913 0.000111641 8.29E-05
48 0.000389326 0.000372065 0.000350789 0.000482888
64 0.00118052 0.0010987 0.000770939 0.000408367
96 0.0054682 0.00384442 0.00249066 0.00257223
128 0.0227045 0.00868955 0.00487929 0.0035134
192 0.0923321 0.0412351 0.0160446 0.00996233
256 0.214051 0.0981237 0.039727 0.0274157
384 0.835667 0.593905 0.171508 0.103088
512 1.95026 9.50343 0.425949 0.292215
768 6.60759 32.5627 1.43155 1.04692
1024 15.6639 76.2935 3.35798 2.59858

Извиняюсь за форматирование :(

Как видно вариант 1 оказывается очень плохим. Он проигрывает на больших данных (>=512) варианту 0 за счёт
нелокализованности данных второй матрицы.
Вариант 2 хочет это исправить, и действительно, он лучше на больших данных (>=64), чем оба варианта.
2+OMP ещё быстрее. Так на 1024 выигрыш почти в 6 раз по сравнению с 0.
Вариант 3 я не тестировал, но ожидаю порядок как в 2.
UFO just landed and posted this here
И в-четвёртых автор накапливает memory latency, а не производительность кода измеряет.

Я не понимаю вообще, у него разве не возникло вопросов почему даже параллельный алгоритм работает так же на достаточно больших временах? Логично ведь предположить в данном случае, что основное время тратится на на исполнение инструкций, а на что-то другое(в данном случае ожидание шины памяти).
UFO just landed and posted this here
Cтранно, что еще никто не привел ссылки с Computer Language Benchmarks Game:
shootout.alioth.debian.org/u32/benchmark.php? test=all&lang=gpp&lang2=csharp
Одно «но» — там Mono (правда здесь компиляторы не указаны вообще).

Шарп сливает в 2-3 раза в среднем. Судить о «performance-critical» приложениях по тестам не возьмусь — разница в 3 раза может как нивелироваться, так и увеличиться в разы — всё зависит от областей применения.
Sign up to leave a comment.

Articles