Egy kedves tanuló barátom megkeresett, hogy a python programját írjam át valami compileres nyelvre, mert nagyon lassú. A program a tanulmányi szintjének teljesen megfelel, de kiváló ürügy, hogy az optimalizálásról pár szót ejtsünk, mivel egy naiv algoritmust használt, az algoritmus optimalizálásával jelentős eredményeket lehet leérni.
A programkódokban az előző verzióhoz képest történt változásokat félkövér szedéssel jeleztem.
Na nézzük a programot:
primes=[2,3,5,7]
def IsPrime(num):
eredm=True
for i in primes:
if num%i==0:
eredm=False
return eredm
for num in range(primes[-1],30000,2):
if IsPrime(num):
primes.append(num)
print(len(primes))
Nagyon jó, de a programot azonnal visszadobtam. Nincsenek benne megjegyzések, amik segítenék a kódban való eligazodást.
#### Prímszám kereső program ####
# Készítette Gipsz Jakab
# A program korlátozások nélkül szabadon felhasználható
# Indulásnak megadunk néhány prímet egy listában
primes=[2,3,5,7]
def IsPrime(num):
#Az argumentumban megadott num integert elosztjuk minden
#ismert prímmel. Ha egyikkel sem osztható, akkor True,
#egyébként False értéket adunk vissza
eredm=True
for i in primes:
if num%i==0:
eredm=False
return eredm
# A főprogram.
# A legnagyobb ismert prím+2-től 30 000-ig minden páratlan számot megnézünk
for num in range(primes[-1]+2,30000,2):
#Ha a szám prím, hozzá adjuk az ismert prímek listájához
if IsPrime(num):
primes.append(num)
print(len(primes))
Így már jobban néz ki, és a program mögötti elképzelésekről is van egy kis képünk.
Tegyünk bele egy idő mérést, hogy mit jelent a „lassú”, mivel az SI mértékegységek között ilyet nem találtam:
Az idő méréséhez szükség van a datetime
modulra, ami tartalmazza a now() függvényt, ami az aktuális pontos időt adja vissza.
#### Prímszám kereső program ####
# Készítette Gipsz Jakab
# A program korlátozások nélkül szabadon felhasználható
# Az időméréshez betöltjük a datetime modult
from datetime import datetime
# Indulásnak megadunk néhány prímet egy listában
primes=[2,3,5,7]
def IsPrime(num):
#Az argumentumban megadott num integert elosztjuk minden
#ismert prímmel. Ha egyikkel sem osztható, akkor True,
#egébként False értéket adunk vissza
eredm=True
for i in primes:
if num%i==0:
eredm=False
return eredm
## A főprogram.
# Először progstart változóban rögzítjük a program indulásának időpotját
progstart=now = datetime.now()
# A legnagyobb ismert prím+2-től 30 000-ig minden páratlan számot megnézünk
for num in range(primes[-1]+2,30000,2):
#Ha a szám prím, hozzá adjuk az ismert prímek listájához
if IsPrime(num):
primes.append(num)
#Rögzítjük, hogy mikor ért véget a program
progend=datetime.now()
#Kiírjuk hány prímet találtunk
print(len(primes))
print('Futásidő:',progend-progstart)
Az én gépemen futtatva ez a verzió olyan 26 másodperc alatt futott le
A teszteléshez használt gép
A teszteléshez az én asztali gépemet használtuk.
CPU: Intel(R) Xeon(R) CPU E5-2643 v2 (6 mag 12 szál)
Memória: 32 GB
Háttértár: SSD
Windows 10 64 bit
A program a 12 szálból egyet használ
A számítógép olyan állapotban van, ahogy használni szoktam. Egy pontosabb méréshez frissen telepített Windowsra lenne szükség, az adott esetben azonban nem finom összehasonlítást végzünk. Itt a nagy léptékű változások érdekelnek.
Minden program verziót négy alkalommal futtatok le. A legnagyobb és legkisebb értéket eldobom, a két középső átlagát adom meg. (A mérési tapasztalat, hogy a négy futtatás ideje alig tér el)
Kezdjük el az észszerűsítést
A Python nem a sebességéről híres, elsősorban arra találták ki, hogy a program gyorsan elkészíthető legyen, program futási ideje is ennek van alárendelve. Ebben a programban azonban bőven van tere az optimalizálásnak. Első lépésben csökkentsük a felesleges osztások számát az IsPrime függvényben. Ha egy számról kiderül, hogy osztható valamelyik prímmel, azonnal kilépünk a ciklusból is .
#### Prímszám kereső program ####
# Készítette Gipsz Jakab
# A program korlátozások nélkül szabadon felhasználható
# Az időméréshez betöltjük a datetime modult
from datetime import datetime
# Indulásnak megadunk néhány prímet egy listában
primes=[2,3,5,7]
def IsPrime(num):
#Az argumentumban megadott num integert elosztjuk minden
#ismert prímmel. Ha egyikkel sem osztható, akkor True,
#egébként False értéket adunk vissza
#Ha a szám nem prím, kilép a ciklusból
eredm=True
for i in primes:
if num%i==0:
eredm=False
break #Azonnal kilép a ciklusból
return eredm
# A főprogram.
# A legnagyobb ismert prím+2-től 30 000-ig minden páratlan számot megnézünk
progstart=now = datetime.now()
for num in range(primes[-1]+2,30000,2):
#Ha a szám prím, hozzá adjuk az ismert prímek listájához
if IsPrime(num):
primes.append(num)
progend=datetime.now()
print(len(primes))
print('Futásidő:',progend-progstart)
Ez a program 3,6 másodperc alatt futott le. Ez már jelentős gyorsulás
Csökkentsük tovább az osztások számát
A második feltétel kevésbé triviális, de könnyű belátni, hogy a szám négyzetgyökénél nagyobb számokat biztos, hogy a négyzetgyöknél kisebb számmal kell megszorozni, hogy az eredeti számot kapjuk. Ha a négyzetgyök értékét elértük, akkor már az összes annál kisebb számmal próbálkoztunk.
A négyzetgyökvonást a math
modul tartalmazza.
#### Prímszám kereső program ####
# Készítette Gipsz Jakab
# A program korlátozások nélkül szabadon felhasználható
# Az időméréshez betöltjük a datetime modult
from datetime import datetime
# A négyzetgyök vonáshoz szükség lesz a math modulra
import math
# Indulásnak megadunk néhány prímet egy listában
primes=[2,3,5,7]
def IsPrime(num):
#Az argumentumban megadott num integert elosztjuk minden
#ismert prímmel. Ha egyikkel sem osztható, akkor True,
#egébként False értéket adunk vissza
#Ha a szám nem prím, kilép a ciklusból
eredm=True
vege=int(math.sqrt(num)+1)
for i in primes:
if num%i==0:
eredm=False
break
if i>vege:
break
return eredm
# A főprogram.
# A legnagyobb ismert prím+2-től 30 000-ig minden páratlan számot megnézünk
progstart=now = datetime.now()
for num in range(primes[-1]+2,30000,2):
#Ha a szám prím, hozzá adjuk az ismert prímek listájához
if IsPrime(num):
primes.append(num)
progend=datetime.now()
print(len(primes))
print('Futásidő:',progend-progstart)
Sikerült jelentős teljesítmény növekedést elérni, a program 0,28 másodperc alatt futott le. Ez jelentős javulás. Akárhogy is nézzük, a futásidőt közel az 1%-ára csökkentettük.
A program futása tovább gyorsítható numpy modul és a jit (just in time compiler) használatával. További lehetőség (különösen ilyen sok szálas gép esetén) lehet használni a multiprocessing modult, ami lehetővé teszi egyes programrészek párhuzamos végrehajtását.
Ezek azonban már erősen nyelv függő dolgok, nem ennek a cikknek témája.
Nézzük meg Freepascal használatával
Azért nem csak a páratlan számokat nézem végig, mert a Pascal nem ismeri a for ciklusban a step kiegészítést, természetesen van rá megoldás, de a feladat nem indokolta. A program egyébkén nagy vonalakban megfelel a kiindulási programnak.
A program láthatóan sokkal több programsorból áll, mivel a programnyelv megköveteli, hogy a változókat használat előtt deklaráluk (a var szakasz). Ugyan ezt meg kell ismételni a függvényeken belül is.
Mivel a program nem interpreteres, hanem compileres (a forrásszövegből külön lépésben kell futtatható programot létrehozni) az elkészült program futása gyorsabb, illetve az előre definiált változók segítik a hibakeresést.
// Prímszám rostáló program a Python program optimalizálásról szóló cikkhez
//Bemutató program, szabadon felhasználható
//Készítette Szigetvári Zoltán
//A sysutil ás DateUtils modulok az idő méréséhez szükségesek
uses sysutils, DateUtils;
const
//A maximális vizsgált szám
max=30000;
var
//A pythonnal ellentétben itt előre meg kell adni a változók típusát.
//A primes nevű tömbben tároljuk az eredményt. Mivel memóriánk van bőven, akkora helyet foglalunk, mintha a minden szám prím lenne
primes:array[0..max] of longint;
//A szam a ciklusváltozó
szam:longint;
//Index-ben tároljuk a megtalált prímek sorszámát 0-tól számolva
index:integer;
//A program indulásának időpontja
progstart:TDateTime;
//Az IsPrime függvény minden egyes már megtalált prímmel elosztjuk a vizsgált számot. Ha a maradék nulla (a szám osztható), a függvény false értékkel tér vissza
function IsPrime(num:QWord;darab:Word):boolean;
var
szam: integer;
eredm:boolean;
begin
eredm:=True;
for szam:=0 to darab do
begin
if num mod primes[szam]=0 then
begin
eredm:=false;
end;
IsPrime:=eredm;
end;
end;
Begin
primes[0]:=2;
primes[1]:=3;
primes[2]:=5;
primes[3]:=7;
index:=3;
progstart:=Now;
for szam:=11 to max do
begin
if IsPrime(szam,index) then
begin
index:=index+1;
primes[index]:=szam;
end;
end;
writeln('A program futási ideje',SecondsBetween(Now,progstart));
writeln('A program indulásának és befejezésének időpontja: ',DateTimeToStr(progstart), DateTimeToStr(now));
writeln('A megtalált prímek száma: ',index+1);
End.
A program a tesztgépen néhány tized másodperc alatt futott le. Ez mutatja, hogy a kissé komplikáltabb programfelépítés és a valamivel komplikáltabb fejlesztési folyamat eredményeképpen jelentősen gyorsabban futó program készíthető. Mind az Interpreteres,, mind a compileres programnyelveknek megvan a maga helye.