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
źródło
Odpowiedzi:
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
źródło
Musisz porównać kompilacje do debugowania. Właśnie skompilowałem Twój kod C i otrzymałem
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:
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.
źródło
i
isqrt
, więc to, co jest mierzone.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.
źródło
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).
źródło
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!
źródło
Aby sprawdzić, czy pętla jest optymalizowana, spróbuj zmienić kod na
ans podobnie w kodzie C, a następnie wypisz wartość roota poza pętlą.
źródło
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.
źródło
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
źródło
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:
W C #:
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.
źródło
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.
źródło
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.
źródło
Wydaje mi się, że nie ma to nic wspólnego z samymi językami, a raczej z różnymi implementacjami funkcji pierwiastka kwadratowego.
źródło
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:
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:
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?
źródło