Я нахожу огромные различия в производительности между аналогичным кодом на C и C #.
Код C:
#include <stdio.h>
#include <time.h>
#include <math.h>
main()
{
int i;
double root;
clock_t start = clock();
for (i = 0 ; i <= 100000000; i++){
root = sqrt(i);
}
printf("Time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
}
И C # (консольное приложение):
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
DateTime startTime = DateTime.Now;
double root;
for (int i = 0; i <= 100000000; i++)
{
root = Math.Sqrt(i);
}
TimeSpan runTime = DateTime.Now - startTime;
Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds/1000));
}
}
}
С приведенным выше кодом C # завершается за 0,328125 секунды (версия выпуска), а C - за 11,14 секунды.
C компилируется в исполняемый файл Windows с помощью mingw.
Я всегда считал, что C / C ++ быстрее или, по крайней мере, сопоставим с C # .net. Что именно заставляет C работать в 30 раз медленнее?
EDIT: похоже, что оптимизатор C # удалял корень, поскольку он не использовался. Я изменил корневое назначение на root + = и распечатал итог в конце. Я также скомпилировал C с помощью cl.exe с флагом / O2, установленным для максимальной скорости.
Теперь результаты: 3,75 секунды для C 2,61 секунды для C #
C все еще занимает больше времени, но это приемлемо
источник
Ответы:
Поскольку вы никогда не используете root, компилятор мог удалить вызов для оптимизации вашего метода.
Вы можете попытаться накопить значения квадратного корня в аккумуляторе, распечатать его в конце метода и посмотреть, что происходит.
Изменить: см . Ответ Джальфа ниже
источник
Вы, должно быть, сравниваете отладочные сборки. Я только что скомпилировал ваш код C и получил
Если вы не включите оптимизацию, все ваши тесты будут бесполезны. (И если вы включите оптимизацию, цикл будет оптимизирован. Таким образом, ваш тестовый код тоже ошибочен. Вам нужно заставить его запустить цикл, обычно суммируя результат или аналогично и распечатывая его в конце)
Похоже, что вы измеряете, в основном, «какой компилятор добавляет больше всего накладных расходов на отладку». И оказывается, что ответ - C. Но это не говорит нам, какая программа самая быстрая. Потому что, когда вам нужна скорость, вы включаете оптимизацию.
Между прочим, вы избавите себя от многих головных болей в долгосрочной перспективе, если откажетесь от представления о том, что языки «быстрее» друг друга. У C # не больше скорости, чем у английского.
В языке C есть определенные вещи, которые были бы эффективны даже в наивном неоптимизирующем компиляторе, а есть другие, которые сильно полагаются на компилятор для оптимизации всего. И, конечно же, то же самое касается C # или любого другого языка.
Скорость выполнения определяется:
Хороший компилятор C # даст эффективный код. Плохой компилятор C будет генерировать медленный код. А как насчет компилятора C, который генерирует код C #, который затем можно запустить через компилятор C #? Как быстро это будет работать? У языков нет скорости. Ваш код делает.
источник
i
, иsqrt
это то, что измеряется.Буду краток, он уже отмечен как отвеченный. У C # есть большое преимущество, заключающееся в наличии четко определенной модели с плавающей запятой. Это просто соответствует собственному режиму работы набора инструкций FPU и SSE на процессорах x86 и x64. Это не случайно. JITter компилирует Math.Sqrt () в несколько встроенных инструкций.
Родной C / C ++ обременен годами обратной совместимости. Параметры компиляции / fp: precision, / fp: fast и / fp: strict являются наиболее заметными. Соответственно, он должен вызвать функцию CRT, которая реализует sqrt () и проверяет выбранные параметры с плавающей запятой, чтобы скорректировать результат. Это медленно.
источник
Я разработчик C ++ и C #. Я разрабатывал приложения на C # с момента выхода первой бета-версии платформы .NET, и у меня более 20 лет опыта разработки приложений на C ++. Во-первых, код C # НИКОГДА не будет быстрее, чем приложение C ++, но я не буду вдаваться в подробное обсуждение управляемого кода, того, как он работает, межоперационного уровня, внутреннего устройства управления памятью, системы динамических типов и сборщика мусора. Тем не менее, позвольте мне продолжить, сказав, что все перечисленные здесь тесты дают НЕПРАВИЛЬНЫЕ результаты.
Позвольте мне объяснить: первое, что нам нужно рассмотреть, - это JIT-компилятор для C # (.NET Framework 4). Теперь JIT создает собственный код для ЦП с использованием различных алгоритмов оптимизации (которые, как правило, более агрессивны, чем оптимизатор C ++ по умолчанию, который поставляется с Visual Studio), а набор инструкций, используемый компилятором .NET JIT, более точно отражает фактический ЦП. на машине, чтобы можно было сделать определенные замены в машинном коде, чтобы сократить тактовые циклы и повысить частоту попаданий в кэш конвейера ЦП, а также произвести дополнительную оптимизацию гиперпоточности, такую как переупорядочение инструкций и улучшения, относящиеся к предсказанию ветвлений.
Это означает, что если вы не скомпилируете свое приложение C ++ с использованием правильных параметров для сборки RELEASE (а не сборки DEBUG), ваше приложение C ++ может работать медленнее, чем соответствующее приложение на основе C # или .NET. При указании свойств проекта в приложении C ++ убедитесь, что вы включили «полную оптимизацию» и «предпочитайте быстрый код». Если у вас 64-битная машина, вы ДОЛЖНЫ указать генерацию x64 в качестве целевой платформы, иначе ваш код будет выполняться через подуровень преобразования (WOW64), что существенно снизит производительность.
После того, как вы выполните правильную оптимизацию в компиляторе, я получаю 0,72 секунды для приложения C ++ и 1,16 секунды для приложения C # (оба в сборке выпуска). Поскольку приложение C # очень простое и выделяет память, используемую в цикле, в стеке, а не в куче, на самом деле оно работает намного лучше, чем реальное приложение, связанное с объектами, тяжелыми вычислениями и с большими наборами данных. Таким образом, представленные цифры являются оптимистичными и ориентированы на C # и платформу .NET. Даже с учетом этого предубеждения приложение C ++ выполняется чуть более чем вдвое быстрее, чем эквивалентное приложение C #. Имейте в виду, что компилятор Microsoft C ++, который я использовал, не имел правильного конвейера и оптимизации гиперпоточности (с использованием WinDBG для просмотра инструкций по сборке).
Теперь, если мы используем компилятор Intel (который, кстати, является отраслевым секретом для создания высокопроизводительных приложений на процессорах AMD / Intel), тот же код выполняется за 0,54 секунды для исполняемого файла C ++ против 0,72 секунды при использовании Microsoft Visual Studio 2010 Итак, в итоге окончательные результаты составляют 0,54 секунды для C ++ и 1,16 секунды для C #. Таким образом, код, созданный компилятором .NET JIT, занимает в 214% раз больше времени, чем исполняемый файл C ++. Большая часть времени, потраченного на 0,54 секунды, была связана с получением времени от системы, а не внутри самого цикла!
Чего также не хватает в статистике, так это времени запуска и очистки, которые не включены в тайминги. Приложения C # обычно тратят намного больше времени на запуск и завершение работы, чем приложения C ++. Причина этого сложна и связана с процедурами проверки кода среды выполнения .NET и подсистемой управления памятью, которая выполняет большую работу в начале (и, следовательно, в конце) программы для оптимизации распределения памяти и мусора. коллектор.
При измерении производительности C ++ и .NET IL важно смотреть на ассемблерный код, чтобы убедиться, что ВСЕ вычисления выполняются. Я обнаружил, что без добавления дополнительного кода на C # большая часть кода в приведенных выше примерах была фактически удалена из двоичного файла. То же самое и с C ++, когда вы использовали более агрессивный оптимизатор, такой как тот, который поставляется с компилятором Intel C ++. Результаты, которые я предоставил выше, верны на 100% и проверены на уровне сборки.
Основная проблема в том, что множество форумов в Интернете состоит в том, что многие новички слушают маркетинговую пропаганду Microsoft, не понимая технологии, и делают ложные заявления о том, что C # быстрее, чем C ++. Утверждается, что теоретически C # быстрее, чем C ++, потому что JIT-компилятор может оптимизировать код для ЦП. Проблема с этой теорией состоит в том, что в .NET framework существует множество проблем, снижающих производительность; сантехника, которой нет в приложении C ++. Кроме того, опытный разработчик будет знать, какой компилятор использовать для данной платформы, и будет использовать соответствующие флаги при компиляции приложения. На платформах Linux или с открытым исходным кодом это не проблема, потому что вы можете распространять исходный код и создавать сценарии установки, которые компилируют код с использованием соответствующей оптимизации. На платформе Windows или с закрытым исходным кодом вам придется распространить несколько исполняемых файлов, каждый с определенной оптимизацией. Двоичные файлы Windows, которые будут развернуты, зависят от ЦП, обнаруженного установщиком msi (с использованием настраиваемых действий).
источник
Моя первая догадка - оптимизация компилятора, потому что вы никогда не используете root. Вы просто назначаете его, а затем перезаписываете снова и снова.
Редактировать: блин, бей на 9 секунд!
источник
Чтобы увидеть, оптимизируется ли цикл, попробуйте изменить свой код на
Аналогично в коде C, а затем распечатайте значение root вне цикла.
источник
Возможно, компилятор C # замечает, что вы нигде не используете root, поэтому он просто пропускает весь цикл for. :)
Возможно, это не так, но я подозреваю, что независимо от причины, это зависит от реализации компилятора. Попробуйте скомпилировать вашу программу C с помощью компилятора Microsoft (cl.exe, доступного как часть win32 sdk) с оптимизацией и режимом выпуска. Бьюсь об заклад, вы увидите улучшение производительности по сравнению с другим компилятором.
РЕДАКТИРОВАТЬ: Я не думаю, что компилятор может просто оптимизировать цикл for, потому что он должен знать, что Math.Sqrt () не имеет побочных эффектов.
источник
Независимо от разницы во времени. может быть, что "прошедшее время" неверно. Он будет действительным только в том случае, если вы можете гарантировать, что обе программы работают в одинаковых условиях.
Может тебе стоит попробовать выиграть. эквивалент $ / usr / bin / time my_cprog; / usr / bin / time my_csprog
источник
Я собрал (на основе вашего кода) еще два сопоставимых теста на C и C #. Эти двое пишут меньший массив, используя оператор модуля для индексации (это добавляет небольшие накладные расходы, но эй, мы пытаемся сравнить производительность [на приблизительном уровне]).
Код C:
В C #:
Эти тесты записывают данные в массив (так что среде выполнения .NET не должно быть разрешено отбирать sqrt op), хотя массив значительно меньше (не хотелось использовать избыточную память). Я скомпилировал их в конфигурации выпуска и запускал из окна консоли (вместо запуска через VS).
На моем компьютере программа C # варьируется от 6,2 до 6,9 секунды, а версия C - от 6,9 до 7,1.
источник
Если вы выполните пошаговый код на уровне сборки, включая пошаговую процедуру извлечения квадратного корня, вы, вероятно, получите ответ на свой вопрос.
Нет необходимости в обоснованных предположениях.
источник
Другой фактор, который может быть проблемой здесь, заключается в том, что компилятор C компилируется в общий машинный код для целевого семейства процессоров, тогда как MSIL, сгенерированный при компиляции кода C #, затем JIT-компиляция для нацеливания на точный процессор, который у вас есть в комплекте с любым возможные оптимизации. Таким образом, собственный код, созданный на C #, может быть значительно быстрее, чем на C.
источник
Мне кажется, что это не связано с самими языками, а скорее связано с различными реализациями функции извлечения квадратного корня.
источник
На самом деле, ребята, цикл НЕ оптимизируется. Я скомпилировал код Джона и проверил полученный .exe. Внутренности петли следующие:
Если среда выполнения не достаточно умна, чтобы понять, что цикл ничего не делает и пропускает его?
Изменить: изменение C # на:
Результаты по затраченному времени (на моей машине) увеличились с 0,047 до 2,17. Но разве это просто накладные расходы на добавление 100 миллионов операторов сложения?
источник