Сравнение скорости c ++ и c #

Меня беспокоила скорость C #, когда он имеет дело с тяжелыми вычислениями, когда вам нужно использовать чистую мощность процессора.

Я всегда думал, что C ++ намного быстрее, чем C #, когда дело касается вычислений. Итак, я провел несколько быстрых тестов. Первый тест вычисляет простые числа ‹целое число n, второй тест вычисляет несколько панцифровых чисел. Идея второго теста исходит отсюда: Панцифровые числа

Простое вычисление C #:

using System;
using System.Diagnostics;

class Program
{

    static int primes(int n)
    {

        uint i, j;
        int countprimes = 0;

        for (i = 1; i <= n; i++)
        {
            bool isprime = true;

            for (j = 2; j <= Math.Sqrt(i); j++)

                if ((i % j) == 0)
                {
                    isprime = false;
                    break;
                }

            if (isprime) countprimes++;
        }

        return countprimes;
    }



    static void Main(string[] args)
    {
        int n = int.Parse(Console.ReadLine());
        Stopwatch sw = new Stopwatch();

        sw.Start();
        int res = primes(n);
        sw.Stop();
        Console.WriteLine("I found {0} prime numbers between 0 and {1} in {2} msecs.", res, n, sw.ElapsedMilliseconds);
        Console.ReadKey();
    }
}

Вариант C ++:

#include <iostream>
#include <ctime>
#include <cmath>

int primes(unsigned long n) {
unsigned long i, j;
int countprimes = 0;
  for(i = 1; i <= n; i++) {
      int isprime = 1;
      for(j = 2; j < sqrt((float)i); j++) 
          if(!(i%j)) {
        isprime = 0;
        break;
   }
    countprimes+= isprime;
  }
  return countprimes;
}

int main() {
 int n, res;
 cin>>n;
 unsigned int start = clock();

 res = primes(n);
 int tprime = clock() - start;
 cout<<"\nI found "<<res<<" prime numbers between 1 and "<<n<<" in "<<tprime<<" msecs.";
 return 0;
}

Когда я запустил тест, пытаясь найти простые числа <100 000, вариант C # завершился за 0,409 секунды, а вариант C ++ - за 0,614 секунды. Когда я запустил их для 1000000, C # закончил за 6,039 секунды, а C ++ - примерно за 12,987 секунды.

Пандигитальный тест на C #:

using System;
using System.Diagnostics;

class Program
{
    static bool IsPandigital(int n)
    {
        int digits = 0; int count = 0; int tmp;

        for (; n > 0; n /= 10, ++count)
        {
            if ((tmp = digits) == (digits |= 1 << (n - ((n / 10) * 10) - 1)))
                return false;
        }

        return digits == (1 << count) - 1;
    }

    static void Main()
    {
        int pans = 0;
        Stopwatch sw = new Stopwatch();
        sw.Start();

        for (int i = 1; i <= 123456789; i++)
        {
            if (IsPandigital(i))
            {
                pans++;
            }
        }
        sw.Stop();
        Console.WriteLine("{0}pcs, {1}ms", pans, sw.ElapsedMilliseconds);
        Console.ReadKey();
    }
}

Пандигитальный тест на C ++:

#include <iostream>
#include <ctime>

using namespace std;

int IsPandigital(int n)
    {
        int digits = 0; int count = 0; int tmp;

        for (; n > 0; n /= 10, ++count)
        {
            if ((tmp = digits) == (digits |= 1 << (n - ((n / 10) * 10) - 1)))
                return 0;
        }

        return digits == (1 << count) - 1;
    }


int main() {
   int pans = 0;
   unsigned int start = clock();

   for (int i = 1; i <= 123456789; i++)
   {
      if (IsPandigital(i))
      {
        pans++;
      }
   }
   int ptime = clock() - start;
   cout<<"\nPans:"<<pans<<" time:"<<ptime;  
   return 0;
}

Вариант C # выполняется за 29,906 секунды, а C ++ - примерно за 36,298 секунды.

Я не трогал никаких переключателей компилятора, и программы на C # и C ++ были скомпилированы с параметрами отладки. Прежде чем я попытался запустить тест, я беспокоился, что C # будет сильно отставать от C ++, но теперь кажется, что разница в скорости в пользу C # довольно большая.

Кто-нибудь может это объяснить? C # не работает, а C ++ скомпилирован в собственном коде, поэтому нормально, что C ++ будет быстрее, чем вариант C #.

Спасибо за ответы!

Я переделал все тесты для конфигурации Release.

Первый тест (простые числа)

C # (числа ‹100,0000): 0,189 секунды C ++ (числа‹ 100,0000): 0,036 секунды

C # (числа ‹1,000,000): 5,300 секунд C ++ (числа‹ 1,000,000): 1,166 секунды

Второй тест (панцифровые числа):

C #: 21,224 секунды C ++: 4,104 секунды

Итак, все изменилось, теперь C ++ намного быстрее. Моя ошибка в том, что я провел тест конфигурации отладки. Могу ли я увидеть некоторое улучшение скорости, если я запустил исполняемые файлы C # через ngen?

Причина, по которой я попытался сравнить C # и C ++, заключается в том, что я знаю некоторые основы обоих, и я хотел изучить API, имеющий дело с графическим интерфейсом. Я думал, что WPF хорош, поэтому, учитывая, что я нацелен на рабочий стол, я хотел увидеть, может ли C # обеспечить достаточную скорость и производительность, когда дело доходит до использования чистой мощности процессора для вычисления различных вычислений (файловые архиваторы, криптография, кодеки и т. Д.) . Но, к сожалению, C # не успевает за C ++, когда дело касается скорости.

Итак, я предполагаю, что навсегда останусь с этим вопросом Сложный вопрос по WPF, Win32 , MFC, а я новичку найду подходящий API.


person Mack    schedule 23.03.2010    source источник
comment
Кто-нибудь может это объяснить? C # не работает, а C ++ скомпилирован в собственном коде, поэтому нормально, что C ++ будет быстрее, чем вариант C #. ‹- Не в режиме отладки.   -  person Billy ONeal    schedule 23.03.2010
comment
... Программы на C ++ были скомпилированы с опциями отладки. Тогда почему мы беспокоимся о производительности?   -  person GManNickG    schedule 23.03.2010
comment
`= 2; j ‹(i ^ (1/2)); j ++) `Это неправильный код. Вы делаете побитовое - или, не возведение в степень.   -  person Billy ONeal    schedule 23.03.2010
comment
Когда вы все исправите и запустите в режиме выпуска, опубликуйте свои результаты.   -  person spender    schedule 23.03.2010
comment
Очень похоже на stackoverflow.com/questions/686483 /   -  person Matthew Flaschen    schedule 23.03.2010
comment
Кроме того, как написано (после исправления C ++) он вполне может вычислять sqrt(i) на каждой итерации цикла, и в этом случае вы действительно будете сравнивать только реализации с квадратным корнем.   -  person Mike Seymour    schedule 23.03.2010
comment
@Matthew Flaschen: Похоже, но не идентично. Другой вопрос касался в основном операций с плавающей запятой, в то время как этот вопрос ориентирован на целые числа. Хотя основная предпосылка остается все та же.   -  person Aaronaught    schedule 23.03.2010
comment
@BillyONeal, мне стыдно за свою ошибку, я исправил.   -  person Mack    schedule 23.03.2010
comment
@Mack: Нет причин для стыда. Мы все делаем ошибки: P Включили ли вы оптимизацию для своих обновлений времени?   -  person Billy ONeal    schedule 23.03.2010
comment
Переделал тесты. Все изменилось.   -  person Mack    schedule 23.03.2010
comment
Если вы ищете библиотеку пользовательского интерфейса C ++, не сбрасывайте со счетов Qt. С ним очень приятно работать.   -  person Swoogan    schedule 15.09.2014
comment
просто потому, что C ++ быстрее, не означает, что вы не можете использовать C # для своего пользовательского интерфейса или даже для всего приложения. То, что он медленнее, не означает, что он не может удовлетворить ваши требования. Я бы посоветовал попытаться сделать то, что вы можете на C #, и если производительность будет такой, какой вы хотите после оптимизации вашей логики (чтобы включить многопоточность, где это возможно), вы можете написать несколько библиотек C ++ и вызвать необходимые методы из вашего приложения C #.   -  person xtreampb    schedule 01.03.2017


Ответы (10)


Почему вы предполагаете, что jit-код медленнее, чем собственный код? Единственным штрафом за скорость будет фактическое дрожание, которое происходит только один раз (вообще говоря). Учитывая программу с продолжительностью работы 30 секунд, мы говорим о незначительной части общей стоимости.

Я думаю, вы можете спутать jit-код с интерпретируемым кодом, который компилируется построчно. Между ними есть довольно значительная разница.

Как указывали другие, вам также необходимо запустить это в режиме выпуска; В режиме отладки отключается отключается большинство оптимизаций, поэтому обе версии будут работать медленнее, чем должны (но на разную величину).

Изменить. Я должен указать еще на одну вещь, а именно на то, что эта строка:

for (j = 2; j <= Math.Sqrt(i); j++)

Это невероятно неэффективно и может помешать тестированию производительности. Вы должны вычислять Math.Sqrt(i) вне внутреннего цикла. Возможно, это замедлит обе версии на эквивалентную величину, но я не уверен, что разные компиляторы будут выполнять разные оптимизации.

person Aaronaught    schedule 23.03.2010
comment
+1. C ++ больше страдает от отключенных оптимизаций, потому что он полагается на встраивание для повышения производительности. Кроме того, JIT будет выполнять некоторые оптимизации на C #, даже если двоичный файл был создан в режиме отладки. - person Billy ONeal; 23.03.2010
comment
Он не только отключает большинство оптимизаций, но и может добавить кучу посторонних проверок ошибок. Visual Studio, например, добавляет проверку переполнения, проверку кучи и другие интересные вещи в конфигурацию режима отладки по умолчанию. - person kibibu; 23.03.2010

Вам необходимо скомпилировать C ++ в режиме выпуска и включить оптимизацию, чтобы получить желаемые результаты производительности.

person Axel Gneiting    schedule 23.03.2010

простой генератор в C ++ неверен

я ^ (1/2) == я xor 0

^ - это побитовый оператор xor, а / - целочисленное деление.

1-е изменение, это правильно, но неэффективно: поскольку i xor 0 == i, сито останавливается не на sqrt (i), а на i.

2-е изменение:

Просеивание можно сделать немного более эффективным. (Вам нужно только вычислить sqrt (n)). Вот как я реализовал Сито Эратосфена для собственного использования (хотя это и в C99):

void sieve(const int n, unsigned char* primes)
{
        memset(primes, 1, (n+1) * sizeof(unsigned char));

        // sieve of eratosthenes
        primes[0] = primes[1] = 0;
        int m = floor(sqrt(n));
        for (int i = 2; i <= m; i++)
                if (primes[i]) // no need to remove multiples of i if it is not prime
                        for (int j = i; j <= (n/i); j++)
                                primes[i*j] = 0;
}
person sisis    schedule 23.03.2010
comment
Однако мне интересно, действительно ли это замедляет его работу, потому что версия C # должна фактически оценивать Math.Sqrt(i) на каждой итерации, что во много раз дороже, чем XOR с одной инструкцией, поэтому эта небольшая ошибка может фактически привести к Версия C ++ быстрее, потому что обе версии программы неэффективны. - person Aaronaught; 23.03.2010
comment
@Aaronaught: за исключением того, что версия C # зацикливается на Sqrt (i), а версия C ++ зацикливается на i. - person Billy ONeal; 23.03.2010
comment
@BillyONeal: Совершенно верно, для больших значений n версия C ++ всегда будет медленнее, потому что она больше зацикливается. Я имел в виду, что тест был сделан чувствительным к n, и этого не должно быть. - person Aaronaught; 23.03.2010
comment
@Aaronaught: Я сомневаюсь, что C # его пересчитает. Ничто в цикле не изменит этого. Напротив, он должен быть явно кэширован в C ++. - person GManNickG; 23.03.2010
comment
@Aaronaught: Верно, но я думаю, что размер данных примера (1000000) в этом случае квалифицируется как большой N :) - person Billy ONeal; 23.03.2010
comment
@GMan: Вы можете знать, что ничто в цикле не изменит его, но компилятор C # не может сделать этого предположения. Он будет пересчитывать его на каждой итерации. Если вы мне не верите, попробуйте. - person Aaronaught; 23.03.2010
comment
@Aaronaught: Я нахожу это удивительным. По крайней мере, в C ++, если бы определение sqrt было видимым, оно никогда бы не пересчитало его. - person GManNickG; 23.03.2010
comment
@GMan: Вот почему я поднял этот вопрос; любая зависимость от конкретных оптимизаций компилятора может исказить результаты. Важно пройти честное испытание. - person Aaronaught; 23.03.2010

Это занимает намного больше времени, потому что алгоритм неправильный.

for(j = 2; j < (i^(1/2)); j++) 

такой же как

for(j = 2; j < (i^0); j++) 

такой же как

for(j = 2; j < i; j++) 

i намного больше, чем sqrt (i). Если посмотреть на просто время работы, оно на порядок больше, чем должно быть в реализации C ++.

Кроме того, как все говорят, я не думаю, что имеет смысл проводить тестирование производительности в режиме отладки.

person Chris    schedule 23.03.2010
comment
Я исправил ошибку, извините. Я попробую запустить их и в режиме релиза. - person Mack; 23.03.2010

Перекомпилируйте программу на C ++ с включенной полной оптимизацией и повторно запустите тесты. C # jit оптимизирует код при его jit, поэтому вы сравнили оптимизированный код C # /. NET с неоптимизированным C ++.

person Brian Ensink    schedule 23.03.2010

Во-первых, никогда не проводите такие тесты в режиме отладки. Чтобы получить значимые числа, всегда используйте режим выпуска.

JIT имеет то преимущество, что знает платформу, на которой он работает, в то время как предварительно скомпилированный код может быть неоптимальным для платформы, на которой он работает.

person Lucero    schedule 23.03.2010
comment
Может быть, но, конечно, есть накладные расходы на выполнение компиляции на этой платформе. - person Billy ONeal; 23.03.2010

Это стойкий миф о том, что JIT-компилятор в управляемом коде генерирует машинный код, который намного менее эффективен, чем тот, который генерируется компилятором C / C ++. Управляемый код обычно выигрывает в управлении памятью и математике с плавающей запятой, C / C ++ обычно выигрывает, когда оптимизатор кода может тратить намного больше времени на оптимизацию кода. В общем, управляемый код составляет около 80%, но он полностью зависит от 10% кода, в котором программа проводит 90% своего времени.

Ваш тест этого не покажет, вы не включили оптимизатор, и оптимизировать особо нечего.

person Hans Passant    schedule 23.03.2010
comment
Управляемый код обычно побеждает в управлении памятью ‹- Потому что программисты C / C ++ обычно ленивы и просто используют гигантские буферы вместо чего-то вроде std::vector<t>, что является обычным для языков JIT. Скорости как скомпилированного кода, так и кода JIT должны быть примерно одинаковыми, все сказано и сделано, если вы не учитываете время, необходимое для загрузки джиттера в память (которое может быть довольно большим в случае Java: P ) +1 - person Billy ONeal; 23.03.2010
comment
Управляемый код вряд ли может выиграть в операциях с плавающей запятой, потому что текущий .Net JIT не выдает никаких инструкций SSE. - person Jasper Bekkers; 23.03.2010
comment
Их больше одного, у x64 есть. Пример: stackoverflow.com/ questions / 686483 / - person Hans Passant; 23.03.2010
comment
Кажется, что x64 JIT совершенно другой, когда дело доходит до концепции: 32-битный JIT и 64-битный JIT были реализованы двумя разными командами в Microsoft с использованием двух разных кодовых баз. 32-битная JIT была разработана командой CLR, тогда как 64-битная JIT была разработана командой Visual C ++ и основана на кодовой базе Visual C ++. Поскольку 64-битная JIT была разработана командой C ++, она больше осведомлена о проблемах, связанных с C ++. - person Mack; 23.03.2010
comment
Да, x86 JITter датируется ~ 1998 годом. Был ли тогда SSE2? x64 датируется ~ 2003 годом и действительно создается другой командой. Не команда C ++. Он никогда не генерирует код из программы C ++, он генерирует код из IL. - person Hans Passant; 23.03.2010

Оба теста недействительны, потому что вы скомпилировали без оптимизации.

Первый тест бессмысленен даже как сравнение неоптимизированного поведения из-за ошибки в вашем коде; Math.Sqrt(i) возвращает квадратный корень из i, i^(1/2) возвращает i - так что C ++ выполняет гораздо больше работы, чем C #.

В общем, это бесполезно - вы пытаетесь создать синтетический тест, который практически не имеет отношения к использованию в реальном мире.

person JoeG    schedule 23.03.2010

ребята, прежде чем сравнивать скорости программ друг с другом, пожалуйста, потрудитесь прочитать несколько статей об инструкциях процессора, сборке, управлении кешем и т. д. А автор просто до смешного забавный приятель. Проверка работоспособности отладочных сборок.

Билли О'Нил - в чем разница между выделением большого буфера и использованием только небольшой его части и использованием динамически выделяемого объекта, такого как вектор, в словах на низком языке? После того, как был выделен большой буфер - никто не беспокоится о неиспользованном материале. Никаких дополнительных операций поддержки не требуется. В то время как для динамических вещей, таких как вектор, постоянная проверка границ памяти не требовала, чтобы убежать от нее. Помните, что программисты на C ++ не только ленивы (что вполне верно, я признаю), но они еще и умны.

person Alexander Solonsky    schedule 23.03.2010

Как насчет этого:

for(sqrti = 1; sqrti <= 11112; sqrti++) {
  int nexti = (1+sqrti)*(1+sqrti)
  for (i = sqrti*sqrti; i < nexti; i++)
  {
    int isprime = 1;
    for(j = 2; j < sqrti; j++) 
        if(!(i%j)) {
      isprime = 0;
      break;
    }
  }

} countprimes + = isprime; }

person Chris    schedule 23.03.2010