web-dev-qa-db-fra.com

Pourquoi mon calcul est-il tellement plus rapide en C # qu'en Python

Vous trouverez ci-dessous un simple processus codé en C# et Python (pour ceux qui sont curieux du processus, il s'agit de la solution au problème n ° 5 de Project Euler ).

Ma question est, le code C# ci-dessous ne prend que 9 secondes pour itérer, tandis que l'achèvement du code Python prend 283 secondes (pour être exact, 283 secondes sur Python 3.4.3 - 64 bits et 329 secondes sur Python 2.7.9 - 32 bits) .

Jusqu'ici, j'ai codé des processus similaires dans C# et Python et les différences de temps d'exécution étaient comparables. Cette fois cependant, il existe une différence extrême entre les temps écoulés.

Je pense qu'une partie de cette différence provient du type de variable flexible du langage python (je suppose que python convertit une partie des variables en double), mais cela reste difficile à expliquer.

Qu'est-ce que je fais mal?

Mon système: Windows-7 64 bits,

C # - VS Express 2012 (9 secondes)

Python 3.4.3 64 bits (283 secondes)

Python 2.7.9 32 bits (329 secondes)

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

et code 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))
13
ssd

La réponse est simplement que Python traite les objets pour tout et qu'il n'a pas JIT par défaut. Donc, plutôt que d’être très efficace en modifiant quelques octets sur la pile et en optimisant les parties les plus chaudes du code (c’est-à-dire l’itération), Python effectue des bugs avec des objets riches représentant des nombres et aucune optimisation à la volée.

Si vous avez essayé cela dans une variante de Python qui a JIT (par exemple, PyPy), je vous garantis que vous verrez une différence énorme.

Une astuce générale consiste à éviter l'utilisation de Python standard pour des opérations très coûteuses en calcul (particulièrement s'il s'agit d'un backend servant des requêtes provenant de plusieurs clients). Java, C #, JavaScript, etc. avec JIT sont incomparablement plus efficaces.

À propos, si vous voulez écrire votre exemple d'une manière plus pythonique, vous pouvez le faire comme ceci:

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)

Le ci-dessus exécuté en 90 secondes pour moi. La raison pour laquelle il est plus rapide repose sur des choses apparemment stupides, telles que x étant plus court que start, que je n'attribue pas autant de variables et que je me fie aux propres structures de contrôle de Python plutôt qu'à la vérification variable pour sauter dans les boucles.

23
Blixt

TL; DR: Un post long qui tente de défendre Python (mon langage de choix) contre C #. Dans cet exemple, C # fonctionne mieux, mais nécessite toujours plus de lignes de code pour effectuer le même travail, mais l’avantage final en termes de performances est que C # est environ 5 fois plus rapide qu’une approche similaire en Python lorsqu’il est codé correctement. Le résultat final est que vous devriez utiliser le langage qui vous convient.

Lorsque j’exécutais l’exemple C #, cela prenait environ 3 secondes sur ma machine et me donnait un résultat de 232 792 560. Il peut être optimisé en utilisant le fait connu que vous ne pouvez avoir qu'un nombre divisible par des nombres de 1 à 20 si le nombre est un multiple de 20 et que, par conséquent, vous n'avez pas besoin d'incrémenter de 1, mais plutôt de 20. Cette optimisation unique fait que le code soit exécuté environ 10 fois plus rapidement en 353 millisecondes.

Lorsque j’ai utilisé l’exemple Python, j’ai abandonné l’attente et essayé d’écrire ma propre version à l’aide d’itertools, qui n’a pas eu beaucoup plus de succès et qui prenait aussi longtemps que votre exemple. Ensuite, j’ai trouvé une version acceptable d’itertools, à condition que seuls les multiples de mon plus grand nombre puissent être divisibles par tous les nombres, du plus petit au plus grand. En tant que tel, le code raffiné Python (3.6) se trouve ici avec une fonction de minutage du décorateur qui affiche le nombre de secondes nécessaires à l'exécution:

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

Cela m'a également rappelé une question à laquelle j'ai récemment dû répondre sur les conflits de codes pour le plus petit commun utilisant la fonction du plus grand dénominateur commun en Python. Ce code est le suivant:

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

Comme dans la plupart des tâches de programmation, l'approche la plus simple n'est parfois pas toujours la plus rapide. Malheureusement, cela a vraiment résisté lors d'une tentative en Python cette fois. Ceci dit, la beauté de Python réside dans la simplicité d’obtenir une exécution performante, où il a fallu 10 lignes de C #, j’ai pu renvoyer la réponse correcte dans une expression (potentiellement) lambda à une ligne, et 300 fois plus rapide que ma optimisation simple sur C #. Je ne suis pas un spécialiste de C #, mais le code que j'ai utilisé et son résultat (environ 5 fois plus rapide que Python) sont implémentés de la même manière:

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

Cependant, pour la plupart des tâches de niveau supérieur, je vois généralement Python se débrouiller exceptionnellement bien par rapport à une implémentation .NET, bien que je ne puisse pas justifier les revendications pour le moment, mis à part le fait que la bibliothèque Python Requests m'a permis de doubler triple rendement en performance par rapport à un WebRequest C # écrit de la même manière. Cela était également vrai lors de l'écriture de processus Selenium, car je pouvais lire des éléments de texte en Python en 100 millisecondes ou moins, mais la récupération de chaque élément prenait C #> 1 seconde pour revenir. Cela dit, je préfère réellement l'implémentation C # en raison de son approche orientée objet, dans laquelle l'implémentation Selenium de Python devient fonctionnelle, ce qui est parfois très difficile à lire.

5
Solonotix

Essayez les implémentations JIT python telles que pypy et numba ou cython si vous voulez un traitement rapide en C mais sacrifiez un peu de lisibilité du code.

par exemple en pypy

# PyPy

number 232792560

time elapsed = 4.000000 sec.

par exemple en cython

# Cython

number 232792560

time elapsed = 1.000000 sec.

Cython Source:

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

Python (et tous les langages de script, y compris matlab) n'est pas destiné à être utilisé directement pour le calcul numérique à grande échelle. Pour obtenir un résultat compatible en tant que programme conforme, évitez les boucles à tout prix et convertissez la formule en formats matriciels (nécessitant un peu de compréhension et de compétences mathématiques), afin que nous puissions Pousser le plus possible vers la bibliothèque C en arrière-plan fourni par numpy, scipy, etc.

Encore une fois, NE PAS écrire de boucles pour le calcul numérique en python, chaque fois qu'une matrice équivalente est possible!

0
Ralph B.

Tout d’abord, vous devez changer l’algorithme pour résoudre ce problème:

#!/usr/bin/env python

import sys
from timeit import default_timer as timer

pyver = sys.version_info;

print(">")
print(">  Smallest multiple of 2 ... K");
print(">")
print(">  Python version, interpreter version: {0}.{1}.{2}-{3}-{4}".format(
    pyver.major, pyver.minor, pyver.micro, pyver.releaselevel, pyver.serial))
print(">")

K = 20;

print("  K = {0:d}".format(K))
print("")

t0 = timer()

N = K
NP1 = N + 1
N2 = (N >> 1) + 1
vec = range(0, NP1)
smalestMultiple = 1

for i in range(2, N2):
    divider = vec[i]
    if divider == 1:
        continue
    for j in range(i << 1, NP1, i):
        if (vec[j] % divider) == 0:
            vec[j] /= divider

for i in range(2, NP1):
    if vec[i] != 1:
        smalestMultiple = smalestMultiple * vec[i]

t1 = timer()

print("  smalest multiple = {0:d}".format(smalestMultiple))
print("  time elapsed = {0:f} sec.".format(t1 - t0))

Sortie sur processeur i7-2760QM Linux/Fedora 28/Intel (R) Core (TM) à 2,40 GHz:

>  Smallest multiple of 2 ... K
>
>  Python version, interpreter version: 2.7.15-final-0
>
>  K = 20
>
>  smalest multiple = 232792560
>  time elapsed = 0.000032 sec.
0
Dariusz Knocinski