2015-04-27 14 views
10

Poniżej znajduje się prosty proces kodowany odpowiednio w C# i Python (dla tych, którzy są ciekawi tego procesu, jest to rozwiązanie problemu nr 5 z Project Euler).Dlaczego moje obliczenia są o wiele szybsze w języku C# niż w Pythonie?

pytanie jest kod C# poniżej trwa tylko w ciągu 9 sekund iteracyjne, a zakończenie kodu Python zajmuje 283 sekund (ściślej, 283 sekund na Python 3.4.3 - 64 bitów i 329 sekund na Python 2.7.9 - 32 bity).

Do tej pory kodowałem podobne procesy zarówno w C#, jak i Python, a różnice czasu wykonania były porównywalne. Tym razem istnieje jednak ogromna różnica między czasami, które upłynęły.

Myślę, że część tej różnicy wynika z elastycznego typu zmiennego języka python (podejrzewam, python konwertuje część zmiennych na podwójne), ale to wciąż jest trudne do wyjaśnienia.

Co robię źle?

Mój system: Windows 7 64 bity,

C# - VS ekspresowe 2012 (9 sekund)

Python 3.4.3 64 bitów (283 sekund)

Python 2.7.9 32 bity (329 sekund)

C ostre kod:

using System; 

namespace bug_vcs { 
    class Program { 
     public static void Main(string[] args) { 
      DateTime t0 = DateTime.Now; 
      int maxNumber = 20; 
      bool found = false; 
      long start = maxNumber; 
      while (!found) { 
       found = true; 
       int i = 2; 
       while ((i < maxNumber + 1) && found) { 
        if (start % i != 0) { 
         found = false; 
        } 
        i++; 
       } 
       start++; 
      } 
      Console.WriteLine("{0:d}", start - 1); 
      Console.WriteLine("time elapsed = {0:f} sec.", (DateTime.Now - t0).Seconds); 
      Console.ReadLine(); 
     } 
    } 
} 

i kod pyton:

from datetime import datetime 

t0 = datetime.now() 
max_number = 20 
found = False 
start = max_number 
while not found: 
    found = True 
    i = 2 
    while ((i < max_number + 1) and found): 
     if (start % i) != 0: 
      found = False 
     i += 1 
    start += 1 

print("number {0:d}\n".format(start - 1)) 

print("time elapsed = {0:f} sec.\n".format((datetime.now() - t0).seconds)) 
+9

Powinieneś użyć 'StopWatch' zamiast' DateTime' do obliczenia czasów wykonania w C# – juharr

+4

I 'timeit' dla Pythona. – jonrsharpe

+0

W odpowiedzi na komentarz @ juharr: W C# prawie nie stoję przy stole. Z drugiej strony z łatwością wypijam filiżankę kawy na dole, gdy Python się iteruje. – ssd

Odpowiedz

14

Po prostu Python zajmuje się obiektami do wszystkiego i nie ma domyślnie wartości JIT. Więc zamiast być bardzo skutecznym poprzez modyfikowanie kilku bajtów na stosie i optymalizację gorących części kodu (tj. Iteracji) - Python chuguje razem z bogatymi obiektami reprezentującymi liczby i bez optymalizacji "w locie".

Jeśli wypróbowałeś to w wariancie Pythona z JIT (na przykład PyPy), gwarantuję ci ogromną różnicę.

Ogólna wskazówka to unikanie standardowego języka Python dla bardzo kosztownych operacji (szczególnie jeśli jest to backend obsługujący żądania od wielu klientów). Java, C#, JavaScript itd. Z JIT są nieporównywalnie bardziej wydajne.

Przy okazji, jeśli chcesz napisać swój przykład w bardziej pythonic sposób, można zrobić to tak:

from datetime import datetime 
start_time = datetime.now() 

max_number = 20 
x = max_number 
while True: 
    i = 2 
    while i <= max_number: 
     if x % i: break 
     i += 1 
    else: 
     # x was not divisible by 2...20 
     break 
    x += 1 

print('number:  %d' % x) 
print('time elapsed: %d seconds' % (datetime.now() - start_time).seconds) 

Zrealizowana w 90 sekund dla mnie powyżej. Powód, dla którego jest szybszy, polega na pozornie głupich rzeczach, takich jak x, które są krótsze niż start, że nie przypisuję zmiennych tak często, i że polegam na własnych strukturach kontrolnych Pythona, a nie na sprawdzaniu zmiennych, aby wskakiwać do/z pętli.

+3

W międzyczasie pobrałem 'pypy' i wypróbowałem mój oryginalny kod; czas wykonania spadł do 10 sekund. Następnie wypróbowałeś swój kod (ponownie na pypie) i poprawiłeś czas do 5 sekund. To ma coś wspólnego z pytonem. – ssd

+1

@ merkez3110: Tak, jak wspomniałem w mojej odpowiedzi, PyPy ma JIT, który zasadniczo optymalizuje kod na znacznie niższym poziomie. Eliminuje to wiele problemów z Pythonem, na przykład zamiast wykonywać matematykę na obiektach z metodami operatora, wykonuje matematykę bezpośrednio na liczbach całkowitych, które są na stosie (jak to robi C#). Mówiąc o stosie, Willa Python przechowuje zmienne w dyktafonie, dlatego nawet zmiana 'start' na' x' poprawia wydajność w vanilla Python. – Blixt

+1

Właściwie w myślach, mogę mieszać język Python z innymi językami interpretowanymi pod względem nazw zmiennych wpływających na wydajność. Możliwe, że Python optymalizuje wyszukiwanie zmiennych w etapie interpretacji, aby nazwy zmiennych nie wpływały na wydajność. Tak czy inaczej, reszta mojego posta powinna być dokładna. – Blixt

0

Wypróbuj implementacje JIT Pythona, takie jak pypy i numba lub cython, jeśli chcesz szybko jako C, ale poświęć trochę czytelności kodu.

e.gw pypy

# PyPy 

number 232792560 

time elapsed = 4.000000 sec. 

np Cython

# Cython 

number 232792560 

time elapsed = 1.000000 sec. 

Cython Źródło:

from datetime import datetime 

cpdef void run(): 
    t0 = datetime.now() 
    cdef int max_number = 20 
    found = False 
    cdef int start = max_number 
    cdef int i 
    while not found: 
     found = True 
     i = 2 
     while ((i < max_number + 1) and found): 
      if (start % i) != 0: 
       found = False 
      i += 1 
     start += 1 

    print("number {0:d}\n".format(start - 1)) 

    print("time elapsed = {0:f} sec.\n".format((datetime.now() - t0).seconds)) 
3

TL; DR: rozwlekły post, który jest mi próbuje bronić Python (mój język z wyboru) przeciwko C#. W tym przykładzie C# działa lepiej, ale nadal pobiera więcej linii kodu, aby wykonać tę samą pracę, ale ostateczną korzyścią z wydajności jest to, że C# jest ~ 5x szybsze niż podobne podejście w Pythonie, gdy jest poprawnie kodowane. W rezultacie powinieneś użyć języka, który Ci odpowiada.

Po uruchomieniu przykładu C# ukończenie pracy na moim komputerze zajęło około 3 sekund i dało mi wynik 232,792,560. Można go zoptymalizować za pomocą znanego faktu, że liczba może być podzielna tylko na liczby od 1 do 20, jeśli liczba jest wielokrotnością 20, a zatem nie trzeba zwiększać o 1, ale zamiast tego. sprawił, że kod został wykonany ~ 10 razy szybciej w zaledwie 353 milisekund.

Po uruchomieniu przykładu Pythona, zrezygnowałem z oczekiwania i próbowałem napisać własną wersję przy pomocy itertools, która nie odniosła większego sukcesu i trwała tak długo jak twój przykład. Potem trafiam na akceptowalną wersję itertools, jeśli weźmiemy pod uwagę, że tylko wielokrotności mojej największej liczby mogą być podzielne na wszystkie liczby od najmniejszej do największej. Jako taki, rafinowany Python (3.6) Kod jest tutaj z funkcją dekorator rozrządu, który drukuje liczbę sekund zajęło wykonanie:

import time 
from itertools import count, filterfalse 


def timer(func): 
    def wrapper(*args, **kwargs): 
     start = time.time() 
     res = func(*args, **kwargs) 
     print(time.time() - start) 
     return res 
    return wrapper 


@timer 
def test(stop): 
    return next(filterfalse(lambda x: any(x%i for i in range(2, stop)), count(stop, stop))) 


print("Test Function") 
print(test(20)) 
# 11.526668787002563 
# 232792560 

to również przypomniał mi pytanie Niedawno miałem odpowiedzieć na CodeFights dla Najmniej częste używanie wielkiej funkcji wspólnego mianownika w Pythonie. Ten kod jest następujący:

import time 
from fractions import gcd 
from functools import reduce 


def timer(func): 
    def wrapper(*args, **kwargs): 
     start = time.time() 
     res = func(*args, **kwargs) 
     print(time.time() - start) 
     return res 
    return wrapper 


@timer 
def leastCommonDenominator(denominators): 
    return reduce(lambda a, b: a * b // gcd(a, b), denominators) 


print("LCM Function") 
print(leastCommonDenominator(range(1, 21))) 
# 0.001001596450805664 
# 232792560 

Podobnie jak w większości zadań programistycznych, czasami najprostsze podejście nie zawsze jest najszybsze. Niestety, to naprawdę wystawało, gdy tym razem spróbowano w Pythonie. To powiedziawszy, piękno w Pythonie to prostota uzyskania wykonania wykonawczego, gdzie zajęło 10 linii C#, udało mi się zwrócić poprawną odpowiedź w (potencjalnie) jednoliniowym wyrażeniu lambda i 300 razy szybciej niż mój prosta optymalizacja na C#. Nie jestem specjalistą w C#, ale wdrożenie tego samego podejścia Oto kod użyłem i jego wynik (około 5x szybciej niż Python):

using System; 
using System.Diagnostics; 

namespace ConsoleApp1 
{ 
    class Program 
    { 
     public static void Main(string[] args) 
     { 
      Stopwatch t0 = new Stopwatch(); 
      int maxNumber = 20; 

      long start; 
      t0.Start(); 
      start = Orig(maxNumber); 
      t0.Stop(); 

      Console.WriteLine("Original | {0:d}, {1:d}", maxNumber, start); 
      // Original | 20, 232792560 
      Console.WriteLine("Original | time elapsed = {0}.", t0.Elapsed); 
      // Original | time elapsed = 00:00:02.0585575 

      t0.Restart(); 
      start = Test(maxNumber); 
      t0.Stop(); 

      Console.WriteLine("Test | {0:d}, {1:d}", maxNumber, start); 
      // Test | 20, 232792560 
      Console.WriteLine("Test | time elapsed = {0}.", t0.Elapsed); 
      // Test | time elapsed = 00:00:00.0002763 

      Console.ReadLine(); 
     } 

     public static long Orig(int maxNumber) 
     { 
      bool found = false; 
      long start = 0; 
      while (!found) 
      { 
       start += maxNumber; 
       found = true; 
       for (int i=2; i < 21; i++) 
       { 
        if (start % i != 0) 
         found = false; 
       } 
      } 
      return start; 
     } 

     public static long Test(int maxNumber) 
     { 
      long result = 1; 

      for (long i = 2; i <= maxNumber; i++) 
      { 
       result = (result * i)/GCD(result, i); 
      } 

      return result; 
     } 

     public static long GCD(long a, long b) 
     { 
      while (b != 0) 
      { 
       long c = b; 
       b = a % b; 
       a = c; 
      } 

      return a; 
     } 
    } 
} 

Dla większości zadań wyższego poziomu, jednak zazwyczaj zobaczyć Python robiąc wyjątkowo dobrze w porównaniu do implementacji .NET, chociaż nie mogę uzasadnić roszczeń w tym momencie, poza tym, że biblioteka Pytań Pythona dała mi tyle, co podwójny do potrójnego zwrot w wydajności w porównaniu do C# WebRequest napisanego w ten sam sposób . Było to również prawdą podczas pisania procesów Selenium, ponieważ mogłem odczytać elementy tekstowe w Pythonie w ciągu 100 milisekund lub mniej, ale każde pobranie elementu zajęło C#> 1 sekundę, aby powrócić. Powiedziałem, że tak naprawdę wolę implementację C# ze względu na podejście obiektowe, w którym implementacja Selenium w języku Python jest funkcjonalna, co czasami jest bardzo trudne do odczytania.

Powiązane problemy