Perfomance im Vergleich zu C++

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
DaPeter
User
Beiträge: 2
Registriert: Sonntag 5. August 2012, 16:43

Sonntag 5. August 2012, 17:04

Hallo,

ich war vorher eher im Bereich C++/Java unterwegs, muss und möchte nun für ein Praktikum aber auch mal Python lernen.
Dazu habe ich mir mal wieder die Fragen aus dem Project Euler rausgesucht, da ich damit in der Vergangenheit neue Sprachen sehr schnell lernen konnte.

Dass Python in der Geschwindigkeit nicht an manche andere Sprachen rankommt, hatte ich eh erwartet und das ist ja auch nicht schlimm, nur leider hab ich es so extrem nicht erwartet.

Nun konkret eine sehr einfache Aufgabe von Project Euler: "What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20?"

Der Algo wurde in beiden Sprachen identisch umgesetzt. Er ist natürlich an sich nicht der schnellste, aber darum geht es nicht. Mit Python dauert er ganze 13.13 Sekunden, mit C++ (gcc) nur 0.12 Sekunden, mit eingeschalteter Optimierung kompiliert nur 0.08.

Puh, dachte ich mir, das ist ein gewaltiger Unterschied.
Meine Frage: Ist das nur ein sehr unglückliches Beispiel für Python oder ist das die Regel? Liefert Python noch Optimierungsmöglichkeiten? (ohne den Algo zu ändern, sonst ist es ja nicht mehr vergleichbar)

Hier noch die Codes:

Code: Alles auswählen

number = 20
while True:
    smallest = True
    for factor in range(11,21):
        if number%factor!=0:
            smallest = False 
            break
    if smallest:
        print(number)
        break
    number+=20

Code: Alles auswählen

#include <iostream>
using namespace std;

int main()
{
    bool smallest;
    unsigned int factor;
    unsigned int number = 20;
        
    while(true) 
    {
        smallest = true;
        for(factor=11;factor<21;++factor)
        {
            if(number%factor != 0)
            {
                smallest = false;
                break;
            }
        }
        if(smallest)
        {
            cout << number << "\n";
            break;
        }
        number+=20;
    }
 
    return 0;
}
BlackJack

Sonntag 5. August 2012, 17:56

@DaPeter: Das dürfte die Regel sein. Nur das das bei normalen Programmen nichts ausmacht. Oder musst Du solche Probleme *tatsächlich* für praktische Aufgaben lösen?

Python programmiert man nicht weil die Programme besonders schnell laufen, sondern weil man schnell grössere Programme schreiben kann, die schnell genug sind. Je nach Problem benutzt man auch Python-Bibliotheken die (teilweise) in C oder C++ geschrieben sind, oder Anbindungen an solche Bibliotheken aus dem jeweiligen Problemfeld. Rechnen mit grösseren Datenmengen in mehrdimensionalen Arrays geht zum Beispiel mit `numpy` recht flott. Oder man schreibt sich den Teil der zu langsam ist in Cython (Python-Untermenge die nach C und von da in eine DLL/shared library kompiliert wird) oder C und bindet den in das Python-Programm ein. Wichtig dabei: nur Teile die wirklich messbar zu langsam sind, damit man den Vorteil der Produktivität durch Python nicht verliert.

Das Programm sieht mir übrigens nach einer 1:1 Umsetzung aus. Also nicht wirklich nach Python.
Benutzeravatar
jbs
User
Beiträge: 953
Registriert: Mittwoch 24. Juni 2009, 13:13
Wohnort: Postdam

Sonntag 5. August 2012, 17:59

Oder man probiert PyPy.
[url=http://wiki.python-forum.de/PEP%208%20%28%C3%9Cbersetzung%29]PEP 8[/url] - Quak!
[url=http://tutorial.pocoo.org/index.html]Tutorial in Deutsch[/url]
BlackJack

Sonntag 5. August 2012, 18:06

@jbs: Habe ich mal gemacht mit dem Code aus dem ersten Beitrag:

Code: Alles auswählen

12.711s         f1
 0.345s         f1 (psyco)
 0.322s         f1 (pypy)
Das erste ist die Zeit mit Python 2.6, das zweite die gleiche Funktion mit ``import psyco; psyco.full()`` und das letzte ist das Programm mit `pypy` ausgeführt. Ich habe den Code in eine Funktion gesteckt, weil lokale Namen schneller aufgelöst werden können und weil `psyco` keinen Code auf Modulebene beschleunigt.

Ach ja: `psyco` ist ein JIT-Compiler.
Benutzeravatar
Sr4l
User
Beiträge: 1091
Registriert: Donnerstag 28. Dezember 2006, 20:02
Wohnort: Kassel
Kontaktdaten:

Sonntag 5. August 2012, 18:07

DaPeter hat geschrieben: Puh, dachte ich mir, das ist ein gewaltiger Unterschied.
Meine Frage: Ist das nur ein sehr unglückliches Beispiel für Python oder ist das die Regel? Liefert Python noch Optimierungsmöglichkeiten? (ohne den Algo zu ändern, sonst ist es ja nicht mehr vergleichbar)
Ja und nein. Ja es ist ein unglückliches Beispiel, weil es nur auf Berechnungen setzt. Andererseit nein weil Python, zumindest CPython bei dieser Art von Arbeit schlecht abschneidet. Dafür gibt es aber viele Lösungen.

Code: Alles auswählen

$ time ./test
232792560

real    0m0.133s
user    0m0.108s
sys     0m0.015s

$ time pypy test.py
232792560

real    0m0.484s
user    0m0.046s
sys     0m0.000s

$ time python test.py
232792560

real    0m9.064s
user    0m0.031s
sys     0m0.047s
Benutzeravatar
jbs
User
Beiträge: 953
Registriert: Mittwoch 24. Juni 2009, 13:13
Wohnort: Postdam

Sonntag 5. August 2012, 18:17

Nutzt man die PyPy-toolchain so reduziert sich die Zeit bei mir auf 0.073 Sekunden.
[url=http://wiki.python-forum.de/PEP%208%20%28%C3%9Cbersetzung%29]PEP 8[/url] - Quak!
[url=http://tutorial.pocoo.org/index.html]Tutorial in Deutsch[/url]
BlackJack

Sonntag 5. August 2012, 18:25

Was man auch machen kann ist ein wenig über das Problem nachdenken. Ich habe für das Problem eine BASIC-Lösung für den C64 die 5 Sekunden braucht. Auf einem Rechner mit einem knapp unter 1 MHz 8-Bit-Prozessor und einem scheusslich langsamen, interpretierten BASIC.
DaPeter
User
Beiträge: 2
Registriert: Sonntag 5. August 2012, 16:43

Sonntag 5. August 2012, 18:26

Vielen Dank für die Antworten. Das reicht mir wohl erstmal, da hab ich genug zu lesen.

@BlackJack:
Ja, in der Arbeitsstelle werden eher algorithmische Fragestellungen mit Python betrachtet. Wie genau das abläuft, weiß ich nocht nicht, aber ich denke mal nicht, dass sie nur "reines" Python verwenden und die schlechtere Laufzeit in Kauf nehmen, die werden sich schon auskennen hoffentlich.
Und mein Code sieht sehr nach 1:1 aus, ja. Ich bin erst seit einem Tag an Python dran und denke daher noch in anderen Sprachen, aber das wird noch ;)

Und natürlich kann man das Problem einfach auf eine andere Art schneller lösen, aber das schrieb ich ja bereits im ersten Post.
Benutzeravatar
darktrym
User
Beiträge: 723
Registriert: Freitag 24. April 2009, 09:26

Sonntag 5. August 2012, 18:30

Bei mir ist der Code 1/3 schneller wenn ich den range-Kram außerhalb der Schleife definiere.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
jerch
User
Beiträge: 1633
Registriert: Mittwoch 4. März 2009, 14:19

Sonntag 5. August 2012, 19:15

Mit einem anderen Ansatz (modifiziertes Sieb, welches sich frühere Faktorisierungen merkt) schmilzt der Unterschied dahin (<5x) und CPython schaffts in:

Code: Alles auswählen

$> time python prim_sieve_factors.py
232792560

real    0m0.028s
user    0m0.017s
sys     0m0.010s
Deine Brute-Force-Variante ist ein ungünstiges Bsp., da Pythons Schleifen weit hinter denen in C/C++ liegen.
Benutzeravatar
snafu
User
Beiträge: 5901
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sonntag 5. August 2012, 19:53

@jerch: Kannst den Code zu deinem Ansatz gerne mal posten. Würde mich interessieren. :)
jerch
User
Beiträge: 1633
Registriert: Mittwoch 4. März 2009, 14:19

Sonntag 5. August 2012, 21:57

So siehts in Python aus:

Code: Alles auswählen

def factorize_sequence(num):
    factored_sieve = [False] * num
    prims = {}
    for i in xrange(2, num):
        if not factored_sieve[i]:
            factored_sieve[i] = (i,)
            counts = 1
            for j in xrange(2, num):
                if i*j >= num:
                    break
                if factored_sieve[j]:
                    factored_sieve[i*j] = (i,) + factored_sieve[j]
                    count_i = factored_sieve[i*j].count(i)
                    if count_i > counts:
                       counts = count_i
            prims[i] = counts
    return factored_sieve[2:], prims

def total_product(factors):
    result = 1
    for prim, count in factors.iteritems():
        result *= prim ** count
    return result

sieve, factors = factorize_sequence(21)
#print sieve, factors
print total_product(factors)
Kann man sicher noch weiter optimieren, hatte allerdings keine Lust, da lange dranrum zu überlegen.
Benutzeravatar
jbs
User
Beiträge: 953
Registriert: Mittwoch 24. Juni 2009, 13:13
Wohnort: Postdam

Sonntag 5. August 2012, 23:11

Meine Lösung wäre die hier:

Code: Alles auswählen

def ggt(a,b):
    while a:
            a, b = b%a, a
    return b

def kgv(x,y):
    return x*y/ggt(x,y)

print reduce(kgv, range(11, 21))
[url=http://wiki.python-forum.de/PEP%208%20%28%C3%9Cbersetzung%29]PEP 8[/url] - Quak!
[url=http://tutorial.pocoo.org/index.html]Tutorial in Deutsch[/url]
Antworten