C # vs C - Duża różnica w wydajności

94

Znajduję ogromne różnice w wydajności między podobnym kodem w C anc C #.

Kod C to:

#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);   

}

A C # (aplikacja konsolowa) to:

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));
        }
    }
}

Z powyższym kodem C # kończy się w 0,328125 sekund (wersja wydania), a C zajmuje 11,14 sekundy do uruchomienia.

C jest kompilowany do pliku wykonywalnego systemu Windows przy użyciu mingw.

Zawsze zakładałem, że C / C ++ są szybsze lub przynajmniej porównywalne z C # .net. Co dokładnie powoduje, że C działa ponad 30 razy wolniej?

EDYCJA: Wygląda na to, że optymalizator C # usuwał katalog główny, ponieważ nie był używany. Zmieniłem przypisanie roota na root + = i wydrukowałem sumę na końcu. Skompilowałem również C przy użyciu cl.exe z flagą / O2 ustawioną na maksymalną prędkość.

Wyniki są teraz: 3,75 sekundy dla języka C 2,61 sekundy dla języka C #

C nadal trwa dłużej, ale jest to dopuszczalne

Jan
źródło
18
Sugerowałbym użycie StopWatch zamiast tylko DateTime.
Alex Fort
2
Które flagi kompilatora? Czy oba są kompilowane z włączoną optymalizacją?
jalf
2
A co jeśli używasz -ffast-math z kompilatorem C ++?
Dan McClain
10
Co za fascynujące pytanie!
Robert S.
4
Może funkcja C sqrt nie jest tak dobra, jak ta w C #. Wtedy nie byłby to problem z C, ale z dołączoną do niego biblioteką. Spróbuj wykonać obliczenia bez funkcji matematycznych.
klew

Odpowiedzi:

61

Ponieważ nigdy nie używasz 'root', kompilator mógł usunąć wywołanie, aby zoptymalizować twoją metodę.

Możesz spróbować zebrać wartości pierwiastka kwadratowego w akumulatorze, wydrukować go na końcu metody i zobaczyć, co się dzieje.

Edycja: zobacz odpowiedź Jalfa poniżej

Brann
źródło
1
Małe eksperymenty sugerują, że tak nie jest. Generowany jest kod pętli, chociaż być może środowisko wykonawcze jest na tyle inteligentne, aby go pominąć. Nawet kumulując, C # wciąż bije spodnie C.
Dana
3
Wygląda na to, że problem jest na drugim końcu. C # zachowuje się rozsądnie we wszystkich przypadkach. Jego kod C jest najwyraźniej skompilowany bez optymalizacji
jalf
2
Wielu z was tu nie rozumie. Czytałem wiele podobnych przypadków, w których c # przewyższa c / c ++ i zawsze obaleniem jest zastosowanie optymalizacji na poziomie eksperta. 99% programistów nie ma wiedzy na temat korzystania z takich technik optymalizacji tylko po to, aby ich kod działał nieco szybciej niż kod C #. Przypadki użycia dla c / c ++ są zawężane.
167

Musisz porównać kompilacje do debugowania. Właśnie skompilowałem Twój kod C i otrzymałem

Time elapsed: 0.000000

Jeśli nie włączysz optymalizacji, wszelkie testy porównawcze są całkowicie bezwartościowe. (A jeśli włączysz optymalizacje, pętla zostanie zoptymalizowana. Więc twój kod testowy również jest wadliwy. Musisz wymusić na nim uruchomienie pętli, zwykle przez zsumowanie wyniku lub podobnego i wydrukowanie go na końcu)

Wygląda na to, że mierzysz w zasadzie „który kompilator wstawia najwięcej błędów związanych z debugowaniem”. Okazuje się, że odpowiedź brzmi C. Ale to nie mówi nam, który program jest najszybszy. Ponieważ jeśli chcesz szybkości, włączasz optymalizacje.

Nawiasem mówiąc, na dłuższą metę zaoszczędzisz sobie wielu kłopotów, jeśli porzucisz jakiekolwiek pojęcie, że języki są „szybsze” od innych. C # nie ma większej szybkości niż angielski.

Są pewne rzeczy w języku C, które byłyby wydajne nawet w naiwnym nieoptymalizującym kompilatorze, a są inne, które w dużym stopniu polegają na kompilatorze, aby wszystko zoptymalizować. Oczywiście to samo dotyczy C # lub dowolnego innego języka.

Szybkość wykonania zależy od:

  • platforma, na której pracujesz (system operacyjny, sprzęt, inne oprogramowanie działające w systemie)
  • kompilator
  • twój kod źródłowy

Dobry kompilator C # zapewni wydajny kod. Zły kompilator C wygeneruje powolny kod. A co z kompilatorem C, który wygenerował kod C #, który można następnie uruchomić za pomocą kompilatora C #? Jak szybko by to działało? Języki nie mają szybkości. Twój kod to robi.

jalf
źródło
O wiele bardziej interesująca lektura: blogs.msdn.com/ricom/archive/2005/05/10/416151.aspx
Daniel Earwicker
18
Dobra odpowiedź, ale nie zgadzam się z szybkością języka, przynajmniej przez analogię: stwierdzono, że welsch jest językiem wolniejszym niż większość z powodu dużej częstotliwości długich samogłosek. Ponadto ludzie lepiej zapamiętują słowa (i listy słów), jeśli szybciej wypowiadają. web.missouri.edu/~cowann/docs/articles/before%201993/… en.wikipedia.org/wiki/Vowel_length en.wikipedia.org/wiki/Welsh_language
wyjątek
1
Czy to nie zależy od tego, czego mówiąc: w Welsch chociaż? Wydaje mi się mało prawdopodobne, aby wszystko przebiegało wolniej.
jalf
5
++ Hej ludzie, nie dajcie się tu zboczyć. Jeśli ten sam program działa szybciej w jednym języku niż inny, dzieje się tak, ponieważ generowany jest inny kod asemblera. W tym konkretnym przykładzie 99% lub więcej czasu przejdzie w stan zmienny iisqrt , więc to, co jest mierzone.
Mike Dunlavey
116

Powiem krótko, jest już zaznaczone jako odpowiedź. C # ma wielką zaletę posiadania dobrze zdefiniowanego modelu zmiennoprzecinkowego. Tak się składa, że ​​jest to zgodne z rodzimym trybem działania instrukcji FPU i SSE ustawionych na procesorach x86 i x64. To nie przypadek. JITter kompiluje Math.Sqrt () do kilku instrukcji wbudowanych.

Natywne C / C ++ jest obarczone wieloletnią kompatybilnością wsteczną. Najbardziej widoczne są opcje / fp: precyzyjne, / fp: szybkie i / fp: ścisłe. W związku z tym musi wywołać funkcję CRT, która implementuje sqrt () i sprawdza wybrane opcje zmiennoprzecinkowe, aby dostosować wynik. To jest powolne.

Hans Passant
źródło
66
To dziwne przekonanie wśród programistów C ++, wydaje się, że myślą, że kod maszynowy generowany przez C # różni się w jakiś sposób od kodu maszynowego generowanego przez natywny kompilator. Jest tylko jeden rodzaj. Niezależnie od używanego przełącznika kompilatora gcc lub asemblera wbudowanego, wciąż istnieje tylko jedna instrukcja FSQRT. Nie zawsze jest szybszy, ponieważ wygenerował go język ojczysty, procesor nie dba o to.
Hans Passant,
16
To właśnie rozwiązuje pre-jitting z ngen.exe. Mówimy o C #, a nie Javie.
Hans Passant,
20
@ user877329 - naprawdę? Łał.
Andras Zoltan
7
Nie, jitter x64 używa SSE. Math.Sqrt () zostaje przetłumaczony na instrukcję kodu maszynowego sqrtsd.
Hans Passant
6
Chociaż technicznie nie jest to różnica między językami, JITter .net wykonuje raczej ograniczone optymalizacje w porównaniu z typowym kompilatorem C / C ++. Jednym z największych ograniczeń jest brak obsługi SIMD, co powoduje, że kod często jest około 4x wolniejszy. Brak ujawnienia wielu wewnętrznych cech może być również dużym uciążliwością, ale to zależy w dużej mierze od tego, co robisz.
CodesInChaos
57

Jestem programistą C ++ i C #. Tworzyłem aplikacje C # od pierwszej wersji beta frameworka .NET i mam ponad 20 lat doświadczenia w tworzeniu aplikacji C ++. Po pierwsze, kod C # NIGDY nie będzie szybszy niż aplikacja w C ++, ale nie będę przechodził długiej dyskusji na temat zarządzanego kodu, jego działania, warstwy międzyoperacyjnej, wewnętrznych elementów zarządzania pamięcią, dynamicznego systemu typów i garbage collectora. Niemniej jednak pozwolę sobie kontynuować, mówiąc, że wszystkie wymienione tutaj testy porównawcze dają NIEPRAWIDŁOWE wyniki.

Pozwól, że wyjaśnię: Pierwszą rzeczą, którą musimy wziąć pod uwagę, jest kompilator JIT dla C # (.NET Framework 4). Teraz JIT tworzy natywny kod dla procesora przy użyciu różnych algorytmów optymalizacji (które są zwykle bardziej agresywne niż domyślny optymalizator C ++ dostarczany z programem Visual Studio), a zestaw instrukcji używany przez kompilator .NET JIT jest bliższym odzwierciedleniem rzeczywistego procesora na maszynie, aby można było wprowadzić pewne podstawienia w kodzie maszynowym, aby zmniejszyć cykle zegara i poprawić współczynnik trafień w pamięci podręcznej potoku procesora oraz uzyskać dalsze optymalizacje hiperwątkowości, takie jak zmiana kolejności instrukcji i ulepszenia dotyczące przewidywania rozgałęzień.

Oznacza to, że jeśli nie skompilujesz aplikacji w języku C ++ przy użyciu poprawnych parametrów dla kompilacji RELEASE (nie kompilacji DEBUG), aplikacja w języku C ++ może działać wolniej niż odpowiadająca jej aplikacja oparta na języku C # lub .NET. Określając właściwości projektu w swojej aplikacji C ++, upewnij się, że włączono „pełną optymalizację” i „preferuj szybki kod”. Jeśli masz maszynę 64-bitową, MUSISZ określić generowanie x64 jako platformy docelowej, w przeciwnym razie kod zostanie wykonany przez podwarstwę konwersji (WOW64), co znacznie zmniejszy wydajność.

Po wykonaniu poprawnych optymalizacji w kompilatorze otrzymuję 0,72 sekundy dla aplikacji C ++ i 1,16 sekundy dla aplikacji C # (obie w kompilacji wydania). Ponieważ aplikacja C # jest bardzo prosta i alokuje pamięć używaną w pętli na stosie, a nie na stercie, w rzeczywistości działa o wiele lepiej niż prawdziwa aplikacja zaangażowana w obiekty, ciężkie obliczenia i większe zestawy danych. Zatem podane liczby są optymistycznymi liczbami ukierunkowanymi na język C # i platformę .NET. Nawet przy takim odchyleniu aplikacja C ++ działa w nieco ponad połowę krótszego czasu niż jej odpowiednik w języku C #. Należy pamiętać, że kompilator Microsoft C ++, którego użyłem, nie miał odpowiedniego potoku i optymalizacji wielowątkowości (użycie WinDBG do przeglądania instrukcji asemblera).

Teraz, jeśli użyjemy kompilatora Intela (który, nawiasem mówiąc, jest tajemnicą branżową, jeśli chodzi o generowanie aplikacji o wysokiej wydajności na procesorach AMD / Intel), ten sam kod jest wykonywany w 0,54 sekundy dla pliku wykonywalnego C ++ w porównaniu z 0,72 sekundy przy użyciu Microsoft Visual Studio 2010 Tak więc ostateczne wyniki to 0,54 sekundy dla C ++ i 1,16 sekundy dla C #. Zatem kod tworzony przez kompilator .NET JIT trwa 214% razy dłużej niż plik wykonywalny C ++. Większość czasu spędzonego w 0,54 sekundy była na pobieraniu czasu z systemu, a nie w samej pętli!

W statystykach brakuje również czasów uruchamiania i czyszczenia, które nie są uwzględnione w chronometrażu. Aplikacje C # spędzają dużo więcej czasu na uruchamianiu i kończeniu pracy niż aplikacje C ++. Przyczyna tego jest skomplikowana i ma związek z procedurami sprawdzania poprawności kodu środowiska uruchomieniowego .NET i podsystemem zarządzania pamięcią, który wykonuje dużo pracy na początku (iw konsekwencji na końcu) programu, aby zoptymalizować alokacje pamięci i śmieci kolektor.

Podczas pomiaru wydajności C ++ i .NET IL ważne jest, aby spojrzeć na kod asemblera, aby upewnić się, że są tam WSZYSTKIE obliczenia. Odkryłem, że bez umieszczania dodatkowego kodu w C #, większość kodu z powyższych przykładów została faktycznie usunięta z pliku binarnego. Tak było również w przypadku C ++, kiedy używałeś bardziej agresywnego optymalizatora, takiego jak ten, który jest dostarczany z kompilatorem Intel C ++. Wyniki, które podałem powyżej są w 100% poprawne i sprawdzone na poziomie montażu.

Głównym problemem z wieloma forami w Internecie jest to, że wielu początkujących słucha propagandy marketingowej firmy Microsoft bez zrozumienia technologii i fałszywie twierdzi, że C # jest szybszy niż C ++. Twierdzi się, że teoretycznie C # jest szybszy niż C ++, ponieważ kompilator JIT może zoptymalizować kod pod kątem procesora. Problem z tą teorią polega na tym, że w środowisku .NET istnieje wiele elementów hydraulicznych, które spowalniają wydajność; hydraulika, która nie istnieje w aplikacji C ++. Ponadto doświadczony programista będzie znał właściwy kompilator do użycia dla danej platformy i użyje odpowiednich flag podczas kompilacji aplikacji. Na platformach Linux lub open source nie stanowi to problemu, ponieważ można dystrybuować źródła i tworzyć skrypty instalacyjne, które kompilują kod przy użyciu odpowiedniej optymalizacji. W systemie Windows lub platformie o zamkniętym kodzie źródłowym będziesz musiał rozpowszechniać wiele plików wykonywalnych, każdy z określonymi optymalizacjami. Pliki binarne systemu Windows, które zostaną wdrożone, są oparte na procesorze wykrytym przez instalator msi (przy użyciu akcji niestandardowych).

Richard
źródło
22
1. Microsoft nigdy nie twierdził, że C # jest szybszy, ale twierdzi, że jest to około 90% szybkości, szybszego programowania (a tym samym więcej czasu na dostrojenie) i bardziej wolne od błędów dzięki bezpieczeństwu pamięci i typów. Wszystkie z nich są prawdziwe (mam 20 lat w C ++ i 10 w C #) 2. Wydajność uruchamiania nie ma znaczenia w większości przypadków. 3. Istnieją również szybsze kompilatory C #, takie jak LLVM (więc przeniesienie Intela to nie Apple to Apples)
ben
13
Wydajność uruchamiania nie jest bez znaczenia. Jest to bardzo ważne w większości aplikacji internetowych dla przedsiębiorstw, dlatego Microsoft wprowadził wstępne ładowanie stron internetowych (autostart) w .NET 4.0. Gdy pula aplikacji jest od czasu do czasu odtwarzana ponownie, pierwsze ładowanie każdej strony spowoduje znaczne opóźnienie dla złożonych stron i spowoduje przekroczenie limitu czasu w przeglądarce.
Richard,
8
We wcześniejszych materiałach marketingowych Microsoft twierdził, że wydajność .NET jest szybsza. Podawali również różne twierdzenia, że ​​moduł odśmiecający miał niewielki lub żaden wpływ na wydajność. Niektóre z tych twierdzeń znalazły się w różnych książkach (o ASP.NET i .NET) we wcześniejszych wydaniach. Chociaż firma Microsoft nie mówi konkretnie, że aplikacja w języku C # będzie szybsza niż aplikacja w języku C ++, może ona zawierać ogólne komentarze i slogany marketingowe, takie jak „Just-In-Time Means Run-It-Fast” ( msdn.microsoft.com/ en-us / library / ms973894.aspx ).
Richard,
71
-1, ten rant jest pełen niepoprawnych i wprowadzających w błąd stwierdzeń, takich jak oczywiste „Kod C # NIGDY nie będzie szybszy niż aplikacja C ++”
BCoates,
32
-1. Powinieneś przeczytać bitwę wydajności C # kontra C Rico Mariani kontra Raymonda Chena: blogs.msdn.com/b/ricom/archive/2005/05/16/418051.aspx . Krótko mówiąc: jeden z najmądrzejszych ludzi w firmie Microsoft wymagał wielu optymalizacji, aby wersja C była szybsza niż prosta wersja C #.
Rolf Bjarne Kvinge
10

moje pierwsze przypuszczenie to optymalizacja kompilatora, ponieważ nigdy nie używasz roota. Po prostu przypisujesz go, a następnie ponownie nadpisujesz.

Edycja: cholera, pobij o 9 sekund!

Neil N.
źródło
2
Mówię, że masz rację. Rzeczywista zmienna jest nadpisywana i nigdy nie jest używana poza tym. CSC najprawdopodobniej po prostu zrezygnowałby z całej pętli, podczas gdy kompilator c ++ prawdopodobnie zostawiłby ją w. Dokładniejszym testem byłoby zgromadzenie wyników i wydrukowanie ich na końcu. Nie należy również na stałe kodować wartości ziarna w, ale raczej pozostawić to zdefiniowane przez użytkownika. To nie dałoby kompilatorowi C # miejsca na pominięcie rzeczy.
7

Aby sprawdzić, czy pętla jest optymalizowana, spróbuj zmienić kod na

root += Math.Sqrt(i);

ans podobnie w kodzie C, a następnie wypisz wartość roota poza pętlą.


źródło
6

Może kompilator C # zauważa, że ​​nigdzie nie używasz roota, więc po prostu pomija całą pętlę for. :)

Może tak nie jest, ale podejrzewam, że niezależnie od przyczyny jest to zależne od implementacji kompilatora. Spróbuj skompilować program w języku C za pomocą kompilatora firmy Microsoft (cl.exe, dostępny jako część zestawu sdk win32) z optymalizacjami i trybem wydania. Założę się, że zauważysz poprawę wydajności w porównaniu z innym kompilatorem.

EDYCJA: Nie sądzę, aby kompilator mógł po prostu zoptymalizować pętlę for, ponieważ musiałby wiedzieć, że Math.Sqrt () nie ma żadnych skutków ubocznych.

i_am_jorf
źródło
2
Może to wie.
2
@Neil, @jeff: Zgoda, może to łatwo wiedzieć. W zależności od implementacji statyczna analiza Math.Sqrt () może nie być taka trudna, chociaż nie jestem pewien, jakie optymalizacje są konkretnie wykonywane.
John Feminella,
5

Niezależnie od różnicy czasu. może być, że „upływający czas” jest nieprawidłowy. Byłoby to ważne tylko wtedy, gdybyś mógł zagwarantować, że oba programy działają w dokładnie takich samych warunkach.

Może powinieneś spróbować wygrać. odpowiednik $ / usr / bin / time my_cprog; / usr / bin / time my_csprog

Tomek
źródło
1
Dlaczego jest to odrzucane? Czy ktoś zakłada, że ​​przerwania i przełączniki kontekstu nie wpływają na wydajność? Czy ktoś może przyjąć założenia dotyczące chybień w TLB, zamiany stron itp.?
Tom
5

Złożyłem (na podstawie twojego kodu) jeszcze dwa porównywalne testy w C i C #. Ci dwaj zapisują mniejszą tablicę, używając operatora modułu do indeksowania (dodaje to trochę narzutu, ale hej, próbujemy porównać wydajność [na prymitywnym poziomie]).

Kod C:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>

void main()
{
    int count = (int)1e8;
    int subcount = 1000;
    double* roots = (double*)malloc(sizeof(double) * subcount);
    clock_t start = clock();
    for (int i = 0 ; i < count; i++)
    {
        roots[i % subcount] = sqrt((double)i);
    }
    clock_t end = clock();
    double length = ((double)end - start) / CLOCKS_PER_SEC;
    printf("Time elapsed: %f\n", length);
}

W C #:

using System;

namespace CsPerfTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = (int)1e8;
            int subcount = 1000;
            double[] roots = new double[subcount];
            DateTime startTime = DateTime.Now;
            for (int i = 0; i < count; i++)
            {
                roots[i % subcount] = Math.Sqrt(i);
            }
            TimeSpan runTime = DateTime.Now - startTime;
            Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds / 1000));
        }
    }
}

Te testy zapisują dane w tablicy (więc środowisko uruchomieniowe .NET nie powinno mieć możliwości usunięcia operacji sqrt), chociaż tablica jest znacznie mniejsza (nie chce używać nadmiernej ilości pamięci). Skompilowałem je w konfiguracji wydania i uruchomiłem je z poziomu okna konsoli (zamiast zaczynać od VS).

Na moim komputerze program C # zmienia się od 6,2 do 6,9 sekundy, podczas gdy wersja C od 6,9 do 7,1.

Cecil ma imię
źródło
5

Jeśli wykonasz tylko jeden krok w kodzie na poziomie zespołu, w tym przechodząc przez procedurę pierwiastkową, prawdopodobnie otrzymasz odpowiedź na swoje pytanie.

Nie ma potrzeby zgadywania.

Mike Dunlavey
źródło
Chciałbym wiedzieć, jak to zrobić
Josh Stodola
Zależy od IDE lub debuggera. Przerwa na początku pgm. Wyświetl okno demontażu i rozpocznij wykonywanie pojedynczych kroków. Jeśli używasz GDB, istnieją polecenia do wykonywania kroków po jednej instrukcji naraz.
Mike Dunlavey
To dobra wskazówka, która pomaga lepiej zrozumieć, co się tam właściwie dzieje. Czy to również pokazuje optymalizacje JIT, takie jak wywołania wewnętrzne i końcowe?
gjvdkamp
FYI: dla mnie pokazało to VC ++ przy użyciu fadd i fsqrt, podczas gdy C # użyło cvtsi2sd i sqrtsd, które, jak rozumiem, są instrukcjami SSE2, a więc znacznie szybsze, jeśli są obsługiwane.
danio,
2

Innym czynnikiem, który może być tutaj problemem, jest to, że kompilator C kompiluje się do ogólnego kodu natywnego dla docelowej rodziny procesorów, podczas gdy plik MSIL wygenerowany podczas kompilowania kodu C # jest następnie kompilowany w JIT, aby ukierunkować na dokładny procesor, który masz kompletny z dowolnym optymalizacje, które mogą być możliwe. Tak więc kod natywny wygenerowany z C # może być znacznie szybszy niż język C.

David M.
źródło
Teoretycznie tak. W praktyce to praktycznie nigdy nie daje wymiernej różnicy. Może procent lub dwa, jeśli masz szczęście.
jalf
lub - jeśli masz określony typ kodu, który używa rozszerzeń, których nie ma na liście dozwolonych dla „ogólnego” procesora. Rzeczy takie jak smaki SSE. Spróbuj z wyższą wartością docelową procesora, aby zobaczyć, jakie dostaniesz różnice.
gbjbaanb
1

Wydaje mi się, że nie ma to nic wspólnego z samymi językami, a raczej z różnymi implementacjami funkcji pierwiastka kwadratowego.

Jack Ryan
źródło
Bardzo wątpię, że różne implementacje sqrt spowodowałyby taką rozbieżność.
Alex Fort
Zwłaszcza, że ​​nawet w języku C # większość funkcji matematycznych jest nadal uważana za krytyczną dla wydajności i jako taka jest zaimplementowana.
Matthew Olenik
fsqrt to instrukcja procesora IA-32, więc implementacja języka nie ma obecnie znaczenia.
Nie jestem pewien,
Wejdź do funkcji sqrt MSVC z debugerem. Robi o wiele więcej niż tylko wykonanie instrukcji fsqrt.
bk1e
1

Właściwie chłopaki, pętla NIE jest optymalizowana. Skompilowałem kod Johna i sprawdziłem wynikowy plik .exe. Wnętrzności pętli są następujące:

 IL_0005:  stloc.0
 IL_0006:  ldc.i4.0
 IL_0007:  stloc.1
 IL_0008:  br.s       IL_0016
 IL_000a:  ldloc.1
 IL_000b:  conv.r8
 IL_000c:  call       float64 [mscorlib]System.Math::Sqrt(float64)
 IL_0011:  pop
 IL_0012:  ldloc.1
 IL_0013:  ldc.i4.1
 IL_0014:  add
 IL_0015:  stloc.1
 IL_0016:  ldloc.1
 IL_0017:  ldc.i4     0x5f5e100
 IL_001c:  ble.s      IL_000a

Chyba że środowisko wykonawcze jest wystarczająco inteligentne, aby zdać sobie sprawę, że pętla nic nie robi i ją pomija?

Edycja: zmiana C # na:

 static void Main(string[] args)
 {
      DateTime startTime = DateTime.Now;
      double root = 0.0;
      for (int i = 0; i <= 100000000; i++)
      {
           root += Math.Sqrt(i);
      }
      System.Console.WriteLine(root);
      TimeSpan runTime = DateTime.Now - startTime;
      Console.WriteLine("Time elapsed: " +
          Convert.ToString(runTime.TotalMilliseconds / 1000));
 }

Wyniki w czasie, który upłynął (na moim komputerze), od 0,047 do 2,17. Ale czy to tylko koszt dodania 100 milionów operatorów dodawania?

Dana
źródło
3
Spojrzenie na IL nie mówi wiele o optymalizacjach, ponieważ chociaż kompilator C # wykonuje pewne rzeczy, takie jak ciągłe zwijanie i usuwanie martwego kodu, IL przejmuje kontrolę i wykonuje resztę w czasie ładowania.
Daniel Earwicker
Pomyślałem, że tak może być. Jednak nawet zmuszając go do działania, nadal jest o 9 sekund szybszy niż wersja C. (W ogóle bym się tego nie spodziewał)
Dana