ugrás a tartalomhoz

Java - text file memóriába?

H.Z. · 2014. Aug. 16. (Szo), 13.53
Mondjátok, java-ban tényleg nincs olyan, mint pythonban ez:
with open("file.txt","r") as infile:
  tomb=f.readlines()

Szóval, hogy egy NL karakterekkel sorokra bontott text file-t egyetlen művelettel töltsek be egy dinamikus méretű String típusú tömbbe?

Akármerre nézelődtem, csak olyat találtam, hogy olvassam be rekordonként (soronként) az egészet, ami viszont jóval lassabban megy, mintha egyetlen művelettel intézném.
(csak teszteléshez kellene, semmi éles dolog ;) )
 
1

Régen nem írtam Java kódot,

Poetro · 2014. Aug. 16. (Szo), 14.36
Régen nem írtam Java kódot, de valahogy így nézne ki:
BufferedReader reader = null;

try {
    reader = new BufferedReader(new FileReader(new File("file.txt")));
    List<String> lines = new ArrayList<String>()
    String line;

    while ((line = reader.readLine()) != null) {
        lines.add(line);
    }

} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        reader.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
Akármerre nézelődtem, csak olyat találtam, hogy olvassam be rekordonként (soronként) az egészet, ami viszont jóval lassabban megy, mintha egyetlen művelettel intézném.

Ezt lemérted? Honnan tudod, hogy jóval lassabb? Lehet, hogy csak Python-ban lassabb?

De persze van másik megoldás is, ugyan ehhez már Java 7 kell (és simán lehet, hogy lassabb, mint az előbbi):
List<String> lines = Files.readAllLines(Paths.get(path), encoding);
2

Konkrétan pythonban néztem,

H.Z. · 2014. Aug. 16. (Szo), 14.48
Konkrétan pythonban néztem, hogy jóval gyorsabban végigmegy ez:
with open("file.txt","r") as f:
   for rec in f:
      pass # itt csinálok vele valamit


mintha egyesével olvasom:
with open("file.txt","r") as f:
  rec=f.readline()
  while rec != "":
     pass # csinálok valamit
     rec=f.readline()


Az első valami bufferelt olvasást végezhet, mert a sebessége nagyjából azonos a nyitó hozzászólásban vázolt két soréval, ha utána megyek végig a memóriában tárolt sorokon.

Az egész meg onnan indult, hogy lassúnak találtam a regexp feldolgozást pythonban, gondoltam, más nyelven talán gyorsabb. Erre kiderült, hogy nagyjából azonos programok közül a perl és a python a leggyorsabb, ha a sorokat regexp segítségével szűröm (50-100%-os eltérések vannak a perl-python javára).

Azóta persze játszottam olyat, hogy fájlból olvasás helyett két stringet adtam oda egymilliószor (vagy tíz-? már nem is tudom) a szűrőnek, így közel egyforma eredményt produkált a java is és a python is.
Viszont itt gyanús, hogy inkább a regex feldolgozó cache-el és ezért egyformák. Ezért kellett volna, hogy betöltsem memóriába az egész fájlt, ahogyan pythonban és azt ugyanúgy dolgozzam fel.
Ugyanis egyelőre van némi bizonytalanság a dologban: lehet, hogy mégis az I/O műveletek kavarnak be a mérésbe. Sajnos a BufferedReader-nek hiába adtam akkora buffer méretet, ami nagyobb, mint az egész fájl, ez nem segített.

Ha a hozzászólásokat ignorálod, csak a topiknyitót nézed, akkor itt a rövid történet: http://hup.hu/node/134550
(hozzászólásokat azért ne, mert teli van flame-mel ;) )


upd: az utolsó sort kerestem azt hiszem. Köszi!
3

VM indulás

Poetro · 2014. Aug. 16. (Szo), 15.55
Az a mérés, azért messze van az igazitól. A VM elindulása JVM esetén jelentősen lassabb mint a többiben, talán a Python VM-je indul leggyorsabban. Szóval tényleg a valós kód futási időt kellene mérni, azaz onnantól, hogy a programod megkapta a vezérlést, odáig, hogy lefutott.
Java esetén a Stringek kezelése jelentősen eltér más nyelvektől, ugyanis ha jól emlékszem minden karaktert mindenképpen több byte-on tárol, lehet hogy akár 3-4-5 byte is szükséges egyetlen karakterhez. Ennél sokkal jobb eredménnyel jár az ember, ha csak byteokat olvas be és kezel, mert akkor nincsen a hatalmas memóriaigény (mondjuk ekkor értelemszerűen elesik az ember a reguláris kifejezésektől).
Egyébként miért nem jó neked a soronkénti beolvasás? Akkor kisebb a memóriaigény, és soronként tudod az adatot feldolgozni, ahogy jön befele az adat, nem kell megvárni, hogy az egészet beolvassa a kód memóriába.
4

Ezzel tisztában vagyok (már

H.Z. · 2014. Aug. 16. (Szo), 16.09
Ezzel tisztában vagyok (már úgy értem: azzal, hogy nem 100%-ig valós a mért idő), de kipróbáltam, hogy rögtön az open előtt kiléptem a programokból, az kb. tizedmásodperces időt jelentett következetesen.
Arról nem beszélve, hogy C programnál végképp nincs VM indulási idő. :)

Ugyanez igaz egyébként az I/O-ra is: ha csak olvasok, de nem csinálok semmit a sorokkal, a futásidő is minimális egy bizonyos fájlméretig és közel azonos a különböző nyelveken írt programok esetében.

Ha a teszthez használt ~700ezer sor helyett 7milliót dolgozok fel fájlból, akkor csökken az eltérés, bár még mindig a python (és a perl, de azt most hagyjuk) a leggyorsabb.

A soronkénti beolvasás végeredményben jó, csak akkor valóban külön kell szórakozni az idő mérésével és jó lett volna, ha két, többé-kevésbé egyformán működő, teljes programot tudok hasonlítgatni.
5

Végül az encoding miatt

H.Z. · 2014. Aug. 16. (Szo), 22.52
Végül az encoding miatt mellőztem ezt a variációt, megírtam így: http://pastebin.com/kgGuVzVM
Már csak azért is, mert kíváncsi voltam, mennyit lassít az I/O az egészen. Hát nem sokat...
A python még így is gyorsabb regex feldolgozásban.
Nem akarom elhinni. :(
6

Java -version; uname -a mit

BlaZe · 2014. Aug. 17. (V), 00.41
Java -version; uname -a mit mond? Nem túl valószínű, de nem lehet, hogy kliens módban fut a jvm?

Nálam így néz ki egyébként a sorrend. Kicsit egyszerűsítettem a patternen hogy legyenek matchelő sorok és kiírattam a ciklus tényleges futási idejét.
$ time java L1 ; time python l1.py ; time ruby l1.rb
1242 ms
240333,136036

real    0m1.307s
user    0m1.553s
sys     0m0.062s
240333 136036

real    0m1.662s
user    0m1.639s
sys     0m0.024s
240333
136036

real    0m2.871s
user    0m2.799s
sys     0m0.073s
Match nélkül amúgy ez az eredmény.
$ time java L1 ; time python l1.py ; time ruby l1.rb
394 ms
0,376369

real    0m0.462s
user    0m0.616s
sys     0m0.074s
0 376369

real    0m1.075s
user    0m1.053s
sys     0m0.023s
0
376369

real    0m2.746s
user    0m2.687s
sys     0m0.060s
$ java -version
java version "1.7.0_03"
Java(TM) SE Runtime Environment (build 1.7.0_03-b04)
Java HotSpot(TM) 64-Bit Server VM (build 22.1-b02, mixed mode)
21

Openjdk 1.7, 64bit, próbáltam

H.Z. · 2014. Aug. 17. (V), 16.28
Openjdk 1.7, 64bit, próbáltam server, client kapcsolóval, meg két, számomra ismeretlen motorral (egyik zero, másik most nem jut eszembe), mindezt xubuntu 14.04-en, virtualbox guestben és ubuntu 12.04-en, utóbbi fizikailag létező vas.
Kernel verzióról nem tudok nyilatkozni, csak ha bekapcsolom őket.

A te változatod viszont közelebb áll ahhoz a sebességhez, amire számítottam. A mintán mit írtál át? Nem tartom kizártnak, hogy összefügg a változtatásod a sebességbeli eltéréssel.

Update: ami itt van az ölemben, 64bites xubuntu 14.04, a java ezt írja ki:
java version "1.7.0_55"
OpenJDK Runtime Environment (IcedTea 2.4.7) (7u55-2.4.7-1ubuntu1)
OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)
25

A mintát erre írtam át,

BlaZe · 2014. Aug. 17. (V), 23.31
A mintát erre írtam át, kicsit módosítottam még rajta, hogy közelebb legyen a tiédhez: ^(\w+\s\d\d)\s(\d\d:\d\d:\d\d)\s(\w+)\s(\w+):\s(\[\w+\.\w+\])\s\w+:\s+(ACCEPT|REJECT|DROP)\s+(.*$)

Az alábbi az eredmény.
$ time java L1; time python l1.py; time java LogReaderT1; time ruby l1.rb
955 ms
162409,213960

real    0m1.019s
user    0m1.237s
sys     0m0.075s
162409 213960

real    0m1.180s
user    0m1.158s
sys     0m0.023s
376369
Elapsed time: 604ms
Known:162409  unknown:213960
Elapsed time: 738ms

real    0m1.407s
user    0m1.838s
sys     0m1.217s
162409
213960

real    0m2.713s
user    0m2.649s
sys     0m0.063s
Én nem nagyon hiszem, hogy a patternek közti különbség számottevő lenne. De ha esetleg tudod anonimizálni a logod, akkor szívesen lefuttatom arra az eredeti patterned. Szóval inkább arra tippelnék, hogy a környezet különböző, amiben futnak a programok. Erőforrással hogy állnak a gépek? CPU, RAM. Mennyi memóriát használ a JVM? Érdemes pl GC-t nézni: java -XX:+PrintGCDetails L1

Ha esetleg tudod anonimizálni a logod, akkor szívesen lefuttatom arra is. Csak érdeklődésből csinálod amúgy, vagy nagyon számít, hogy lehessen még húzni a teljesítményén? Profile-ozni lehet, ha nagyon kíváncsi vagy.

A teljes filet memóriába rántó, majd utána soronként végigtekerő megoldás pedig nem lehet gyorsabb annál, mint amikor azonnal feldolgozod. Ha már egyszer a kezedben van valami adat, az a legoptimálisabb ha fel is dolgozod. Különben még egyszer elő kell rángatni a memóriából. Ami a disk elérésnél összehasonlíthatatlanul gyorsabb, de nincs ingyen. Nagy mennyiségnél ez már okozhat észrevehető különbséget. Emellett ez a verzió nálam full GC-ket is nyomott, míg az eredeti L1 verzió csak minor GC-ket. Ha adtam neki 2G memóriát, akkor nem volt GC és kb 40 ms-sel volt gyorsabb az L1.
26

Részemről merő kíváncsiság,

H.Z. · 2014. Aug. 17. (V), 23.44
Részemről merő kíváncsiság, semmi tétje.
Maga a log 160MB, a java-t volt, hogy 1G heap mérettel indítottam.
Kipróbáltam openjdk-t, oracle-t (mindkettő 1.7), semmi különbség.

Nálam egy i5-2520M procin, virtualboxban fut elsődlegesen, de kipróbáltam fizikai vason is, egy core2duo processzoros, 6-7 éves gépen is, ott asszem, a java is régebbi volt, de ugyanígy 64bites, a python változat ott is gyorsabb volt.

A minta kapcsán csak annyi érdekelt, hogy a zárójeleket nem vetted-e ki, mert azok, illetve a végén a .* esetleg sokat lassíthatnak rajta. (már nem tudom, hol olvastam erről. :( )

Az nem tudom, feltűnt-e, hogy amikor a teljes fájlt memóriába töltöm és ott megyek végig rajta, akkor külön mérem a beolvasásra fordított időt és külön azt, amikor a listán megy végig és ellenőrzi a minta illeszkedését.


De mondom, ez az egész nem arról szól, hogy gyorsítani akarok valamin, pusztán nem akarom elhinni, hogy egy java program, nagyjából azonos működés esetében lassabban fut le, mint egy python.
27

Python amúgy milyen verzió?

BlaZe · 2014. Aug. 18. (H), 00.07
Python amúgy milyen verzió? Itt 2.7.

Az nem tudom, feltűnt-e, hogy amikor a teljes fájlt memóriába töltöm és ott megyek végig rajta, akkor külön mérem a beolvasásra fordított időt és külön azt, amikor a listán megy végig és ellenőrzi a minta illeszkedését.
Persze, de ha csak a listán iterálást nézed, akkor almát körtével. Ha az egészet, akkor meg lassabb. Többek között a fentiek miatt.

Azt kipróbálhatod még amúgy, hogy nem új matchert hozol létre minden iterációra, hanem Matcher.reset()-et hívsz. Valamennyit ez is gyorsít.
28

2.7 szintén.A reset csak

H.Z. · 2014. Aug. 18. (H), 00.17
2.7 szintén.
A reset csak minimálisan gyorsít, nem beszélve arról, hogy a pythonban sem trükközök semmivel.
Viszont... ha a match() helyett find()-t használok és a minta végéről lehagyom a "(.*$)" karaktereket, akkor már gyorsabb a pythonnál (úgy is, hogy a pythonban is kiszedem ezeket a karaktereket a mintából)
Ami legalábbis érdekes.
29

Megtennéd, hogy a minta

H.Z. · 2014. Aug. 18. (H), 19.58
Megtennéd, hogy a minta végéről kiveszed a '(.*$)' karaktereket és úgy is lefuttatod a két tesztet?
Nálam a java-t erősen felgyorsította ez az apróság, a pythont kevésbé.
30

Durván 30%. $ time java L1;

BlaZe · 2014. Aug. 18. (H), 23.06
Durván 30%.

$ time java L1; time python l1.py; time java LogReaderT1; time ruby l1.rb
675 ms
162409,213960

real    0m0.759s
user    0m0.916s
sys     0m0.076s
162409

real    0m1.112s
user    0m1.089s
sys     0m0.023s
376369
Elapsed time: 537ms
Known:0  unknown:376369
Elapsed time: 500ms

real    0m1.132s
user    0m1.596s
sys     0m0.223s
162409
213960

real    0m2.447s
user    0m2.376s
sys     0m0.071s
7

Ha a sebesség számít, a

Hidvégi Gábor · 2014. Aug. 17. (V), 10.32
Ha a sebesség számít, a feladatra próbáltad a linux beépített programjait használni? Érzésre gyorsabb és egyszerűbb lenne a script.
8

Remélem, ezt csak viccnek

H.Z. · 2014. Aug. 17. (V), 10.35
Remélem, ezt csak viccnek szántad...
Úgy gondolod, hogy fut a program és beolvasott soronként indít egy linuxos utility-t? Vagy hogy?
9

Nem, abból indultam ki, hogy

Hidvégi Gábor · 2014. Aug. 17. (V), 10.49
Nem, abból indultam ki, hogy például múltkor volt egy hasonló problémám, ott egy bemenő sql fájlban kellett egy regexp segítségével lecserélni dolgokat, erre a sed-et használtam, amit csak megfelelően meg kellett paraméterezni, és egy sorból megvolt az egész.
10

Nekem viszont van egy készülő

H.Z. · 2014. Aug. 17. (V), 10.57
Nekem viszont van egy készülő programom, ami mintaillesztéssel bont darabokra sorokat és ezt a műveletet kissé lassúnak éreztem. Kipróbáltam más nyelven is, arra számítva, hogy legalább a java gyorsabb lesz, de azt láttam, hogy a java még lassúbb.
11

A mintaillesztésből

Hidvégi Gábor · 2014. Aug. 17. (V), 13.50
A mintaillesztésből összeszedett darabokkal mi a terved?
12

Van jelentősége? Jelenleg

H.Z. · 2014. Aug. 17. (V), 13.57
Van jelentősége?
Jelenleg pusztán az érdekel, hogy miért működik ennyivel lassabban a java regexp motorja a pythonénál.
(Egyébként gondolod, hogy csak l'art pour l'art akarom szétszedni a beolvasott sorokat? ;) )
13

Van

Hidvégi Gábor · 2014. Aug. 17. (V), 14.27
Mint írtam korábban, elképzelhetőnek tartom, hogy egyszerű unix parancsok segítségével is meg lehet akár oldani a feladatot pár sorban.
14

Na jó, akkor ezt most

H.Z. · 2014. Aug. 17. (V), 14.53
Na jó, akkor ezt most hagyjuk!
15

Mit nem értek?

Hidvégi Gábor · 2014. Aug. 17. (V), 15.07
Mit nem értek?
16

Van egy program, amit hosszú

H.Z. · 2014. Aug. 17. (V), 15.19
Van egy program, amit hosszú ideje írogatok, minimális intenzitással.
Észrevettem, hogy lassú a regexp feldolgozás benne, több nagyságrendet gyorsít, ha például a beolvasott szöveg mezőit nem regular expression segítségével szedem szét, hanem ú.n. whitespace karakterek mentén egy split függvénnyel darabolom (amit adott esetben nem tehetek meg sajnos, valójában egy tisztességes parsert kellene írnom, de egyelőre elégséges a regexp használata, kihasználva a python spec. lehetőségeit, pl. a groupokat).

Ennyi volt az előzmény.
Pár napja eszembe jutott mindez és előszedtem a maradék java, ruby, C ismereteimet meg millió tutorialt és megnéztem, hogy más nyelveken mennyivel gyorsabb. És itt ért a meglepetés, mert mind C-ben, mind java-ban jóval lassúbb lett a teszt, (szerintem) azonos mintákkal és azonos input fájlokkal. Természetesen nem egy-egy futtatásról van szó, hanem rengetegről (most már közel járhatok a százas értékhez/nyelv)
A tesztben pont az lenne a lényeg, hogy az adott nyelveken mit lehetne tenni, hogy gyorsabb legyen a minta illesztés, mint pythonban (különösen C és java esetében izgalmas, mert ezek ugye eleve gyorsabbak egyéb téren).
17

Lehet, hogy a Python valami

Hidvégi Gábor · 2014. Aug. 17. (V), 15.28
Lehet, hogy a Python valami régebbi vagy speciális regexp könyvtárat használ.
18

És ettől gyorsabb, mint a

H.Z. · 2014. Aug. 17. (V), 15.36
És ettől gyorsabb, mint a java vagy a C? (megjegyzem, a perl is gyorsabb és nem minimális eltérések vannak, hanem 50-100% körüli, de még ha a mérési hibáimat is beleszámítom, akkor is 30-50% a python-perl duó javára)
Épp azon problémázok, hogy bár a pythont lassúnak találom helyenként, az egyébként jóval gyorsabb programnyelvekben megírva ugyanazt, sokkal lassabban működő kódot kapok.
19

Amit még el tudok képzelni,

Hidvégi Gábor · 2014. Aug. 17. (V), 15.54
Amit még el tudok képzelni, hogy a Pythonnak gyorsabb a string/változókezelése.

Viszont most már feltehetnéd valahova a fájljaidat (a feldolgozandóval együtt), hogy megnézhessem, mert elkezdett érdekelni a dolog.
20

minta

H.Z. · 2014. Aug. 17. (V), 16.26
java
python

A feldolgozáshoz használt állomány egy linuxos kernel log (openwrt syslog-ng kimenet) ilyen jellegű sorokkal:
2014 Jul  2 12:09:36 127.0.0.1 user.warning kernel: DROP IN=ppp0 OUT= MAC= SRC=1.1.1.1 DST=2.2.2.2 LEN=131 TOS=0x00 PREC=0x00 TTL=119 ID=1833 PROTO=UDP SPT=33435 DPT=13467 LEN=111

Ilyenből, csak változó tartalommal (fontos a változó tartalom!!) van kb. 700ezer. A fenti persze a valóságban egy sor, de nem tudtam úgy beilleszteni.
22

Pont egy ilyen változó

Hidvégi Gábor · 2014. Aug. 17. (V), 17.13
Pont egy ilyen változó tartalmú fájlt tehetnél még fel valahova, meg a C-t is.
23

C Bocs, de privát logot nem

H.Z. · 2014. Aug. 17. (V), 17.25
C

Bocs, de privát logot nem szívesen adnék ki.
Nincs kéznél egy darab linuxod sem?
A /var/log/syslog v. messages fájlokban kb. ugyanez van, max. a mintám nem illeszkedik rá, mert az elején a dátum más formátumú.
24

Ok, köszönöm.

Hidvégi Gábor · 2014. Aug. 17. (V), 18.34
Ok, köszönöm.