Почему мои вычисления намного быстрее в С#, чем Python

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

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

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

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

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

Моя система: 64-разрядные версии Windows-7,

С# - VS Express 2012 (9 секунд)

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

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

c-sharp код:

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

Ответ 1

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

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

Общий совет - избегать стандартного Python для очень дорогостоящих операций с вычислением (особенно, если это для бэкэнда, обслуживающего запросы от нескольких клиентов). Java, С#, 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, а не на переменную, проверяющую, чтобы входить/выходить циклов.

Ответ 2

TL; DR: Long-winded post, который я пытаюсь защитить Python (мой язык выбора) против С#. В этом примере С# работает лучше, но все же занимает больше строк кода для выполнения того же объема работы, но конечная производительность - это то, что С# в 5 раз быстрее, чем аналогичный подход в Python при правильной кодировке. Конечным результатом является то, что вы должны использовать язык, который вам подходит.

Когда я запускаю пример С#, мне потребовалось около 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 строк С#, я смог вернуть правильный ответ в (потенциально) однострочном лямбда-выражении и в 300 раз быстрее, чем мой простая оптимизация на С#. Я не специалист по С#, но реализация такого же подхода здесь - это код, который я использовал, и его результат (примерно в 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 дала мне столько же аналогично тому, как это показано в С# WebRequest. Это также верно при написании процессов Selenium, поскольку я мог читать текстовые элементы в Python за 100 миллисекунд или меньше, но для каждого извлечения элементов потребовалось С# > 1 секунда для возврата. Тем не менее, я на самом деле предпочитаю реализацию С# из-за своего объектно-ориентированного подхода, когда реализация Python Selenium становится функциональной, что очень сложно читать в разы.

Ответ 3

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

например, в 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))