Guido van Rossum visszavonul

Fórumok

A Python atyja, Guido van Rossum a továbbiakban nem vesz részt a nyelv fejlesztésére vonatkozó döntéshozatali eljárásokban. Ezt a python-committers levelezési listán jelentette be. Elfáradt, és egészségügyi problémákra is hivatkozik, de úgy tűnik, hogy különösen megviselte a PEP 572 érdekében vívott harc is.

Minden tisztelet megilleti az eddig végzett munkájáért. A PEP 572-ben egy olyan újítás van megfogalmazva, a "nevesített kifejezések" néven, amelyeket személy szerint nagyon hiányoltam a Pythonból, mert a C-ben gyakran használtam a hasonló konstrukciót. Egy példa:

stuff = [ (y := f(x), x/y) for x in range(5)] 

Ahelyett, hogy kétszer számoltatnánk ki az f(x)-et:

stuff = [ (f(x), x/f(x)) for x in range(5)] 

Hozzászólások

:(

-----
„Egy jó kapcsolatban a társunkat az ő dolgában kell támogatni, nem a miénkben.”
rand() a lelke mindennek! :)
Szerinted…

Stílszerűbb lett volna 1,5 év múlva:
https://pythonclock.org/
Akkor még van 1,5 év, hogy a 3-as is legalább ugyanolyan gyors (lassú) legyen, mint a 2-es. :)

Csak Lennart Pöttering ne tudja meg...

--
robyboy

Nem vagyok egy python guru, de a fenti problémára több alternatíva is kínálkozik. Persze az is igaz, hogy a nevesített kifejezés rövidebb.

Normálisan ilyenkor az következne, hogy valaki beindítja a Python4-et, ügyelve arra, hogy ne legyen kompatibilis egyik meglévővel sem.

mwhaha. azert elvarnam egy modern forditotol, hogy a fenti kifejezest kioptimalizalja maga, no es persze rogton tovabbforditsa gepi kodra.

Sajna ezt egyelore egyetlen fordito kepes megcsinalni kovetkezetesen es megbizhatoan, es az a jvm. talan meg a .net-nek van valami hasznalhato jit-je, de ennyi. ez gaz.

Szerintem nem csak az optimalizációval van gond, hanem a kódduplikációval is.

Leteszteltem a lehetséges python jittereket az alábbi kóddal:


import time, re
def f(x):
	back = []
	for i in range(0,x):
		if i not in back:
			back.append(i)
	return back[1:10]

s = time.time()
back = f(20000).index(1)
print("Indexing one", time.time() - s)

s = time.time()
back = f(20000).index(1) + f(20000).index(2)
print("Indexing two", time.time() - s)


pattern = "[A]+" * 12
test_str = "A" * 1000
s = time.time()
[len(re.match(pattern, test_str).group(0)) for i in range(1000)]
print("Matching one", time.time() - s)

s = time.time()
[len(re.match(pattern, test_str).group(0)) for i in range(1000) if re.match(pattern, test_str)]
print("Matching two", time.time() - s)

CPython2


('Indexing one', 1.9690001010894775)
('Indexing two', 3.921999931335449)
('Matching one', 0.05299997329711914)
('Matching two', 0.0969998836517334)

Pypy2 6.0.0


('Indexing one', 0.05799984931945801)
('Indexing two', 0.11500000953674316)
('Matching one', 0.07500004768371582)
('Matching two', 0.10299992561340332)

IronPython + .net4.5


('Indexing one', 4.1027679443359375)
('Indexing two', 8.1740570068359375)
('Matching one', 0.07904052734375)
('Matching two', 0.14720916748046875)

IronPython + .netcore2.0


('Indexing one', 3.7830429077148438)
('Indexing two', 7.5531768798828125)
('Matching one', 0.09737396240234375)
('Matching two', 0.16550445556640625)

Python 3.6 + numba


Indexing one 0.2415463924407959
Indexing two 0.16579389572143555
Matching one 0.07383203506469727
Matching two 0.08798456192016602

Szóval a numba-n kívül a többi nem végzett ilyen jellegű optimalizációt. (.net esetében lehet, hogy az ironpython implementáció miatt)

Érdekességképpen megnéztem javascripttel is:

Chrome


Indexing one: 221.033935546875ms
Indexing two: 437.77392578125ms

Firefox


Indexing one: 184ms
Indexing two: 352ms

Edge


Indexing one: 103,225ms
Indexing two: 202,515ms

Lefuttattam újra a tesztet és rájöttem, hogy fals eredményeket kaptam numba-val: a

f(20000).index(1) + f(20000).index(2)

csak a jit warmup miatt volt gyorsabb, mint a

f(20000).index(1)

futása. Új eredmények numba-val:


Indexing one 0.2547800540924072
Indexing one for second time 0.0816488265991211
Indexing two 0.19306492805480957
Indexing three 0.24411439895629883
Matching one 0.07869338989257812
Matching one second time 0.045740365982055664
Matching two 0.09146356582641602
Matching three 0.1360464096069336

Szóval egyik általam tesztelt jitter sem végzett ilyen jellegű optimalizálást

Haha, ez jo, megcsinaltam ugyanezt java + jmh -val:


    public static int[] f2(int x) {
        int[] back = new int[x];
        int backi = 0;

        for (int i = 0; i < x; i++)
            if (!ArrayUtils.contains(back, i)) back[backi++] = i;

        return ArrayUtils.subarray(back, 0, 10);
    }

    @Benchmark
    public void one2() {
        res = f2(20000)[0];
    }

    @Benchmark
    public void two2() {
        res = f2(20000)[0] + f2(20000)[1];
    }

eredmeny (1 core, 1 thread, egy 2014-es macbookon):


# Run complete. Total time: 00:01:24

Benchmark       Mode  Cnt   Score   Error  Units
VsPython.one2  thrpt   20  11.675 ± 0.228  ops/s
VsPython.two2  thrpt   20   5.735 ± 0.178  ops/s

Egyebkent IMHO logikus elvaras, hogy a fordito tudja/sejtse elore, ha egy metodus nem modosit allapotot (es ezen felul meg lehessen explicite mondani), es aszerint optimalizalja az ismetlodo hivasokat.
Valszeg megneztek ezt, BTW, csak a gyakorlatban eleg maceras ilyet stabilra megcsinalni, keves helyen lehet hasznalni, ott meg eccerubb megcsinalni kezzel.

De ez a pypy2 igen erosre sikerult! Eddig mindenhol azt lattam, hogy meg ha van is JIT, doglassu (lasd .net). Felteszem okkal nem hasznalja ezt mindenki, csak nem tudom, mi az :)

PS: teljes kod elerheto: https://github.com/agoston/benchmark/blob/master/src/test/java/com/bol/…

> De ez a pypy2 igen erosre sikerult! Eddig mindenhol azt lattam, hogy meg ha van is JIT, doglassu (lasd .net). Felteszem okkal nem hasznalja ezt mindenki, csak nem tudom, mi az :)

C-ben írt modul inkompatibilitások/lassulások, illetve (mint általában minden JIT) több memóriát eszik, mint az alap Python. A fenti kód Win10-en: CPython: 4MB, Pypy: 25MB, Numba: 82MB (update: ebből a szempontból valószínű nem mérvadó a teszt, mert egy üres script is kb ennyit eszik)

Nem feltétlenül ekvivalens a kettő, ha f(x)-nek side effectje van, akkor egyáltalán nem mindegy, hogy kétszer, vagy egyszer értékeled ki. Például ha f(x) nyilvántartja meghívásainak számát egy adatmezőben, akkor baromira nem ugyanaz lesz a két kiértékelés eredménye.

De hát pont erről van szó, hogy a fordító tudHATná, hogy pure function-ről van-e szó, van-e mellékhatása, és ha nincs, akkor optimalizálja ki.

Amúgy azt olvastam, hogy ha a függvény elég kicsi, akkor inline-olásra kerül, és akkor már lehet, hogy nem lesz kétszer "meghívva".

De ha kétszer van inline-olva, akkor megint csak kétszer hajdótik végre a side effect. Amúgy azt megállapítani, hogy valami biztosan pure function, ekivalens a halting problemmel, általánosan nem megoldható. Szóval nem, a fordító ezt nem tudhatná biztosan sosem. És ez bizonyítható :)

Akkor nem írtam volna, amit írtam. Azt olvastam, hogy ha inline-olásra kerül a dupla függvényhívás, akkor már ki lesz optimalizálva, mert metóduson belül a JIT már látja, hogy fölösleges valamit újra meghívni. Itt olvastam, persze lehet, hogy félreértettem, mert nem ismerem a Hotspot legmélyebb titkait: https://stackoverflow.com/a/48050831/552139 és https://stackoverflow.com/a/48047090/552139 .

Managed programnyelvekben (pl. Java) elég könnyen megállapítható a dolog. Ha a függvény csak a bemeneti paramétereivel dolgozik (nem ír és nem olvas globális állapotot, nincs I/O, nincs szálkezelés és locking és stb.), és azok immutable típusok, akkor az egy pure function.


public static int add(int a, int b) { return a + b; }

Szerinted mennyire nehéz erről megállapítani, hogy pure function? A kritériumok, amiket fentebb írtam, mindegyik elég könnyen ellenőrizhető. Az más tészta, hogy egyes típusok kieshetnek a túl szigorú szűrés miatt (pl. com.google.common.collect.ImmutableList módosítható, de kizárólag reflection-nel).

A kérdésben teljesen mindegy, hogy managed, vagy nem managed, nem a memórikezelés módja dönti el azt, hogy egy függvény pure-e vagy nem :)

"Ha a függvény csak a bemeneti paramétereivel dolgozik, és azok immutable típusok, akkor az egy pure function"
Na, ez nem igaz.


public static void foo(final Foo param) {
  param.doStuff();
}

ahol Foo:


public class Foo() {
  private final MySingleton singleton;

  public Foo(MySingleton singleton) {
    this.singleton = singleton;
  }

  public void doStuff() {
    singleton.doSideEffect();
  }
}

Foo egy tökéletes immutable osztály. MySingleton attól singleton, hogy egy DI container egyet hoz belőle létre (és nem azért, mert GoF szerinti Singleton).
A foo metódus csak a bemenetként kapott osztállyal dolgozik. Mégis van side effect.

Amit akartál mondani, hogy a bemenetek value classok (minden value class immutable, de nem minden immutable value class).

Szóval még mindig nem triviális probléma ez :)

"egy function nem lehet pure, ha hív olyat, ami nem pure. Semmivel sem bonyolultabb ennek a megállapítása, mint az eddigiek. "
Dehogynem bonyolultabb.
Tegyük fel, hogy a függvényed bemenő argumentuma egy interfész. Nem tudhatod, hogy az implementáció, ami oda megy, pure lesz-e, vagy nem.
Honnan tudod, hogy olyat hív, ami pure, vagy nem? Azt sem tudod, milyen implementáció megy oda be.
Nagyon-nagyon-nagyon szűk azon kódok köre, aminél meg lehet mondani, hogy egy függvény tényleg pure-e (mert mint mondtam, ez ekvivalens a halting problemmel).

Compilerről beszéltünk. Az, hogy ez mikor fut (AOT vagy JIT), totál más kérdés. Amit a C1/C2 JIT fordít valamit vagy a Graal AOT fordít valamit, és inline mellett dönt, az egy compile döntés.

Az egy dolog, hogy a C2 JIT a compile döntését profilozás alapján hozza meg, az AOT meg nem. De nem a runtime mondja meg a compilernek, hogy itt inline-olnod kell (a runtime nem tudja utasítani a compilert), a compiler dönt. Nincs inline kulcsszó, ahol te vezérled a compilert, és fejlesztési időben hozol döntést. Vagy épp a futó C1/C2 compilationt sem tudod vezérelni, hogy mi az, amit mindenképp inline-oljon, mert te tudod, hogy az jó lesz. Java esetén az inlineolás mindig compile time döntés, amit a compiler a rendelkezésére álló információk alapján hoz meg (ezért sincs például Graal AOT estén dynamic classloading támogatva). C1/C2 esetén rendelkezésére áll futási profil. De amint az változhat (mert classload van), akkor egyből jön az, hogy az addigi eredményt eldobja.
Nem tudod a compilert befolyásolni kívülről. Majd ő dönt, amikor compile van.
És amikor futásidőben hoz döntést a C1/C2, az csak bizonyos esetekben jó.
Monomorf esetekben nagyon jó, polimorf esetekben nem annyira jó. Pont azért, mert elvileg sem lehet annyira jó. Persze, lehet isteníteni ezeket a compilereket, de a CHA/devirtualization/profilin nem mindenható ám :)

Azt, hogy valami biztosan pure, már elméletileg sem tudhatja *minden esetben. Azaz akkor is, ha minden osztályt ismered, ami be van töltve, minden interfész minden implementációja fix, akkor sem tudod mindig eldönteni, hogy valami biztosan pure. Ugyanis ez ekvivalens a halting problemmel. Nem tudsz olyan kódot írni, ami mindig, minden esetben megmondja, hogy "f pure function".

Oké, de a kicsit gyengébb megfogalmazás is hasznos tud lenni: egyik kimenet, hogy biztosan pure, a másik kimenet, hogy passz. Ha nincsenek benne egyáltalán azok a dolgok, amiket fent írtam (pl. I/O, metóduson kívüli állapot írása/olvasása, stb.), akkor nagyon könnyű eldönteni, hogy pure (pl. az "int add(int,int)" metódusról simán látjuk, hogy az). Ha ez nem igaz rá, akkor még mindig lehet, hogy pure, de valószínűleg inkább nem az, akkor mondjuk rá azt, hogy passz, és kész. Amikor már olyan szinten bizonytalan a dolog, hogy pl. muszáj minden létező bemenetre leszimulálni a függvényt, és abból talán kijön, hogy ez bizony minden bemenetre pure volt, az tényleg halting problem, de nyilván ilyen szintre semmilyen fordító nem megy el, semmilyen más optimalizáció során sem, csak az egyszerűbb és igazolhatóan korrekt esetekben végeznek el transzformációkat.

Persze. És amint dynamic classload van, a CHA újra elvégződik, ez tök alap dolog, azt hiszem 2 vagy 3 éve tartott a JVM Language Summiton az egyik Azul fejlesztő erről egy nagyon jó előadást. Amint classload van, a devirtualizálás eldobódik.

A CHA egy heurisztika csak, ami a monomorf eseteket kezeli nagyon jól, a polimorf eseteket kevésbé jól. És sokszor el is dobják az eredményét, csak tényleg a hot codepath az, ahol működik.

Na jó, amúgy lelövöm a poént: azért írtam pont az interfészes példát pár kommenttel feljebb, mert interfészre nincs CHA.
https://youtu.be/E9i9NJeXGmM?t=1h11m43s

Java esetén: abszolúte tévedsz.
java compile esetén _nincs_ optimalizáció, hogy minden platform ugyanazt a class-t lássa. Ezért tudsz decompile-olni szinte szó szerint forrással egyezőre.

Mindig, MINDIG a jit optimalizál, futás közben, folyamatosan. Ha reflectionnel belenyúsz a pl. add metódusba (a példa fentebb valahol), újrafordítja és újraoptimalizálja az érintett metódusokat.
minek olvass utána: "HotSpot" compiler

Nem tokeletes a dolog, de C-ben pl. van volatile, direkt arra, hogy ne optimalizaljon ki a fordito olyat, ami mashogy (pl. interruptbol) is valtozhat.
Illetve Postgresql-ben vannak olyan tarolt eljarasok, amikre megadhatod, hogy ez itt mindig ugyanazt adja mellekhatas nelkul, es kioptimalizalhato.

--
When you tear out a man's tongue, you are not proving him a liar, you're only telling the world that you fear what he might say. -George R.R. Martin

Jo; lehet, hogy felszabadultabba valik a fejlodes.

Jon a Microsoft, es jot tesz majd.