2015-04-27 4 views
10

Ниже приведена простая часть процесса, закодированная в C# и Python соответственно (для тех из вас, кто интересуется процессом, это решение проблемы № 5 Project Euler).Почему мои вычисления намного быстрее в C#, чем Python

Мой вопрос, то C# ниже код занимает всего 9 секунд, чтобы перебирать, а завершение Python кода занимает 283 секунд (точнее, 283 секунд на Python 3.4.3 - 64 бита и 329 секунд на Python 2.7.9 - 32 бит).

До сих пор я кодировал аналогичные процессы как в C#, так и в Python, а разницы во времени выполнения были сопоставимы. Однако на этот раз существует крайняя разница между прошедшими временами.

Я думаю, что часть этой разницы возникает из гибкого типа переменных языка python (я подозреваю, что python преобразует часть переменных в двойную), но этого еще трудно объяснить.

Что я делаю неправильно?

Моя система: Windows-7 64 бит,

C# - VS Экспресс 2012 (9 секунд)

Python 3.4.3 64 бит (283 секунд)

Python 2.7.9 32 бит (329 секунд)

с острыми код:

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

и код Python:

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

Вы должны использовать 'StopWatch' вместо' DateTime' для вычисления времени выполнения в C# – juharr

+4

и 'timeit' для Python. – jonrsharpe

+0

В ответ на комментарий @ juharr: На C# я почти не сижу со стола. С другой стороны, я легко могу выпить чашку кофе на два этажа внизу, когда Python выполняет итерацию. – ssd

ответ

14

Ответ просто, что Python имеет дело с объектами, за все, и что он не имеет JIT по умолчанию. Поэтому, вместо того, чтобы быть очень эффективным, изменяя несколько байтов в стеке и оптимизируя горячие части кода (т. Е. Итерацию), Python сочетается с богатыми объектами, представляющими числа, и без оптимизации «на лету».

Если вы попробовали это в варианте Python с JIT (например, PyPy), я гарантирую, что вы увидите огромное различие.

Общий совет - избежать стандартного Python для очень дорогостоящих операций с вычислением (особенно, если это для бэкэнда, обслуживающего запросы от нескольких клиентов). Java, C#, JavaScript и т. Д. С JIT несравнимо более эффективны.

Кстати, если вы хотите написать свой пример в более вещий образом, вы могли бы сделать это следующим образом:

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) 

выше выполняется в течение 90 секунд для меня. Причина, по которой это быстрее, зависит от кажущихся глупыми вещей, таких как x, короче, чем start, что я не часто назначаю переменные, и что я полагаюсь на собственные структуры управления Python, а не на переменную, проверяя, чтобы прыгать в/из циклов.

+3

Между тем, я загрузил 'pypy' и попробовал свой оригинальный код; время выполнения сокращено до 10 секунд. Затем попробовал ваш код (опять же на pypy) и улучшил время до 5 секунд. Итак, это как-то связано с самим python. – ssd

+1

@ merkez3110: Да, как я намекал в своем ответе, PyPy имеет JIT, который существенно оптимизирует код на гораздо более низком уровне. Это устраняет множество проблем с Python, например, вместо того, чтобы выполнять математику по объектам с помощью операторских методов, она выполняет математику непосредственно по целым числам, находящимся в стеке (как C#). Говоря о стеке, vanilla Python хранит переменные в dict, поэтому даже изменение 'start' на' x' улучшает производительность в ванильном Python. – Blixt

+1

На самом деле, во-вторых, я могу смешивать Python с другими интерпретируемыми языками в отношении имен переменных, влияющих на производительность. Возможно, что Python оптимизирует поиск переменных на этапе интерпретации, так что имена переменных не влияют на производительность. В любом случае, остальная часть моего сообщения должна быть точной. – Blixt

0

Попробуйте python JIT Реализации, такие как pypy и numba или cython, если вы хотите быстро, как C, но жертвуйте некоторой удобочитаемостью кода.

e.г в PyPy

# PyPy 

number 232792560 

time elapsed = 4.000000 sec. 

, например, в Cython

# Cython 

number 232792560 

time elapsed = 1.000000 sec. 

Cython Источник:

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: Скучный пост, который я пытаюсь защитить Python (мой язык по выбору) против C#. В этом примере C# работает лучше, но все же занимает больше строк кода для выполнения того же объема работы, но конечная производительность - это то, что C# в 5 раз быстрее, чем аналогичный подход в Python при правильной кодировке. Конечным результатом является то, что вы должны использовать язык, который вам подходит.

Когда я запустил пример C#, на мою машину потребовалось около 3 секунд и дал мне результат 232,792,560. Он может быть оптимизирован с использованием известного факта, что вы можете иметь только число, делящееся цифрами от 1 до 20, если число кратно 20, и поэтому вам не нужно увеличивать на 1, а вместо 20. Эта единственная оптимизация заставил код выполнить ~ 10 раз быстрее всего за 353 миллисекунды.

Когда я запускаю пример Python, я отказался от ожидания и попытался написать свою собственную версию с помощью itertools, которая не имела большого успеха и продолжалась до тех пор, пока ваш пример. Затем я нахожу приемлемую версию itertools, если учесть, что только кратные моего наибольшего числа могут быть делятся на все числа от наименьшего до самого большого. Таким образом, рафинированное Python (3,6) код здесь с функцией декоратор синхронизации, который печатает количество секунд потребовалось выполнить:

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 

Это также напомнило мне вопрос, который я недавно должен был ответить на CodeFights для Наименее распространенный множественный с использованием функции Greatest Common Denominator в Python. Этот код выглядит следующим образом:

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 

Как и в большинстве задач программирования, иногда самый простой подход не всегда является самым быстрым. К сожалению, это действительно застряло при попытке в Python на этот раз. Тем не менее, красота в Python - это простота выполнения исполнительского исполнения, где потребовалось 10 строк C#, я смог вернуть правильный ответ в (потенциально) однострочном лямбда-выражении и в 300 раз быстрее, чем мой простая оптимизация на C#. Я не специалист в C#, но реализация такой же подход здесь код, который я использовал, и его результат (около 5 раз быстрее, чем 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; 
     } 
    } 
} 

Для большинства задач более высокого уровня, тем не менее, я обычно вижу Python делая исключительно хорошо по сравнению с реализацией .NET, хотя в настоящее время я не могу обосновать претензии, не говоря о том, что библиотека Python Requests дала мне столько же, сколько двойное возвращение в производительности по сравнению с C# WebRequest, написанным таким же образом , Это также верно при написании процессов Selenium, поскольку я мог читать текстовые элементы в Python за 100 миллисекунд или меньше, но для каждого извлечения элементов потребовалось C#> 1 секунда для возврата. Тем не менее, я на самом деле предпочитаю реализацию C# из-за своего объектно-ориентированного подхода, когда реализация Selenium от Python становится функциональной, что очень сложно читать порой.