Java DataInputStream - rychlost

Arthur

  • ***
  • 168
    • Zobrazit profil
    • E-mail
Java DataInputStream - rychlost
« kdy: 18. 05. 2023, 16:44:59 »
Zdravím a mám dotaz na zkušenější Javisty:

Načítám číselná data ze souboru (~ stovky MB) do pole v paměti. Data mohou být různého formátu (int8, uint16, float32, ... atd) a proto volám v cyklu DataInputStream.readWhatever(), podle toho co je zrovna potřeba, např. takto:

Kód: [Vybrat]
DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));

float[] pole = new float[size];

for (int i = 0; i < size; i++)
  pole[i] = dis.readFloat();


Všechno funguje, až na to, že první volání tohoto kódu po spuštění aplikace je asi 3x pomalejší než všechna následná volání. Nezáleží na tom, jestli čtu ten samý soubor znovu nebo úplně jiný. Chová se to stejně i když ten soubor načtu do byte[] a použiju ByteArrayInputStream. Paměti je k dispozici řádově víc než velikost pole.

Pokud v tomto konkrétním případě použiju

Kód: [Vybrat]
byte[] buf = new byte[size * 4];
dis.read(buf);
ByteBuffer.wrap(buf).asFloatBuffer.getFloats(pole);

tak je to rychlostně OK, takže by to nemělo souviset s disk IO nebo alokací paměti, či co...


Re:Java DataInputStream - rychlost
« Odpověď #1 kdy: 18. 05. 2023, 19:26:59 »
Vám vadí že první float načtete pomalu? Nebo tomu špatně rozumím? Pro rychlý přístup k datům můžete použít memory - mapped buffer, konkrétně např.
Kód: [Vybrat]
MappedByteBuffer.getFloat()
Viz:


Re:Java DataInputStream - rychlost
« Odpověď #2 kdy: 18. 05. 2023, 19:54:29 »
Co znamená „první volání je pomalejší“? Je pomalejší přečtení prvního celého streamu? Nebo je pomalé první čtení z každého streamu? Nebo první čtení z prvního streamu? Bude to stejně pomalé, když vynecháte DataInputStream nebo BufferedInputStream?

Re:Java DataInputStream - rychlost
« Odpověď #3 kdy: 19. 05. 2023, 07:31:26 »
BufferedInoutStream alokuje buffer v konstruktoru a alokuje 8 KB. To by samo zpomalovat nemělo.

Pokud je to první volání I/O v programu, tak něco může spolknout první vytváření datových struktur pro spolupráci s operačním systémem.

Další možnost je, že při prvním průchodu se ten kód interpretuje a kompiluje a při dalším průchodu se používá kód již kompilovaný, tedy rychlejší. To by odpovídalo JVM typu hotspot, což je nejběžnější případ.

Arthur

  • ***
  • 168
    • Zobrazit profil
    • E-mail
Re:Java DataInputStream - rychlost
« Odpověď #4 kdy: 19. 05. 2023, 10:02:43 »
Pomalé je první čtení celého souboru.  Mám datové soubory velikosti stovek MB a různě s nimi pracuju. S prvním souborem trvá načtení třeba 15s, poté už okolo 5s.  Kdybych to mohl udělat jednoduše přes read(byte[]) v BufferedInputStream a potom jako celek zkonvertovat skrze ByteBuffer, tak je to asi za 2s (vždycky). Ale chci/potřebuju využívat ty metody specifické v DataIntputStreamu. Každopádně 5s je OK, potřebuju urychlit těch 15s.

Co třeba pomůže je načíst nejdřív malý soubor (2-3 MB) předem a pak už i ten první velký jde rychle. Ale když jsem zkusil takové iniciální "předčtení" přidat automaticky k načtení velkého souboru (načíst nejdřív malou část a pak znovu všechno), tak to nepomohlo.

Shrnuto:
DataInputStream:  nejprve 15s a dále 5s
BufferedInputStream:  nejprve 2s a dále 2s
Ano, je to první IO volání po spuštění


Re:Java DataInputStream - rychlost
« Odpověď #5 kdy: 19. 05. 2023, 11:55:23 »
Pokud můžete, zkuste ten MappedByteBuffer. IMHO pro podobné použití ideální, přednačtete potřebné soubory a pak s nimi rychle pracujete, vše je v paměti, čímž eliminujete vliv filesystému apod.

Arthur

  • ***
  • 168
    • Zobrazit profil
    • E-mail
Re:Java DataInputStream - rychlost
« Odpověď #6 kdy: 19. 05. 2023, 13:04:42 »
OK díky,  mrknu na to. 

On by ten samotný ByteBuffer asi nakonec stačil, ale chtěl jsem využít mj. i DataInputStream.readUnsignedByte() a readUnsignedShort() abych se vyhnul ruční konverzi uint8 -> short a uint16 -> int. Předpokládám, že tyto vestavěné funkce jsou efektivnější než když to budu implementovat sám..   ale nakonec na výsledku uvidím


Re:Java DataInputStream - rychlost
« Odpověď #7 kdy: 19. 05. 2023, 13:39:33 »
MappedByteBuffer plus vhodná konverzní/matematická/whatever knihovna - rychlosti bych se nebál, bitové operace a casting musí být rychlé, ale vhodná knihovna případně zpřehlední kód. 

Nejvíc záleží co vlastne děláte, něco se dá prednacist, predpocitat, paralelizovat atd atd.

Re:Java DataInputStream - rychlost
« Odpověď #8 kdy: 19. 05. 2023, 14:29:26 »
Je zpomalené čtení celého souboru, nebo třeba jen první čtení, nebo dokonce ještě před prvním čtením? Každopádně rozdíl 10 sekund je hodně. Když jde jen o první čtení, čekal bych, že tam bude alokace bufferů, ale to nemůže trvat 10 sekund, to je o mnoho řádů jinde. Pak mne ještě napadlo, zda do toho nezasahuje nějaký problém na straně OS, SElinux, NFS, čekání na nějaký timeout – ale pak zase nevidím důvod, proč by se to dělo jenom u prvního souboru.

Arthur

  • ***
  • 168
    • Zobrazit profil
    • E-mail
Re:Java DataInputStream - rychlost
« Odpověď #9 kdy: 19. 05. 2023, 19:36:28 »
Citace
Je zpomalené čtení celého souboru, nebo třeba jen první čtení, nebo dokonce ještě před prvním čtením?

Načítám do paměti celý soubor, viz:
Kód: [Vybrat]
DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));

float[] pole = new float[size];

for (int i = 0; i < size; i++)
  pole[i] = dis.readFloat();

dis.close();

Tohle trvá napoprvé těch 15s. Opakovaně už jen 5s se stejným nebo jiným souborem, třeba i se stejnou kopií toho prvního. Chová se to stejně na Win10 + Java8 Oracle, Ubuntu 20.04 /22.04 + Java11 openJDK. Poměr času je zachován, na rychlejším stroji je to 5s vs 1.7s.

Ještě mě napadlo jestli to nějak nemůže souviset s tím, že to pouštím v samostatném vlákně skrze java.util.concurrent.Executor, protože dokud je to uvnitř vlákna tak i opakované čtení je pořád stejně pomalé, ale jakmile to spustím znovu (v samostaném vlákně) tak už je to OK ...

Re:Java DataInputStream - rychlost
« Odpověď #10 kdy: 19. 05. 2023, 21:25:17 »
Načítám do paměti celý soubor, viz:
Ano, ale můžete změřit dobu trvání každého řádku kódu. Jak dlouho trvá samotné vytvoření proměnné dis, tedy první řádek? Jak dlouho trvá každá obrátka cyklu, tedy volání readFloat()?

Tohle trvá napoprvé těch 15s. Opakovaně už jen 5s se stejným nebo jiným souborem, třeba i se stejnou kopií toho prvního. Chová se to stejně na Win10 + Java8 Oracle, Ubuntu 20.04 /22.04 + Java11 openJDK. Poměr času je zachován, na rychlejším stroji je to 5s vs 1.7s.
Takže to asi nebude věc OS, ale opravdu věc Javy.

Ještě mě napadlo jestli to nějak nemůže souviset s tím, že to pouštím v samostatném vlákně skrze java.util.concurrent.Executor
Pokud tím myslíte, že to spouštíte v jiném, než hlavním vlákně, to na to vliv nemá.

protože dokud je to uvnitř vlákna tak i opakované čtení je pořád stejně pomalé, ale jakmile to spustím znovu (v samostaném vlákně) tak už je to OK ...
Tomuhle nerozumím. Pokaždé to běží uvnitř nějakého vlákna. Jak ten vícevláknový kód vypadá?

alex6bbc

  • *****
  • 1 432
    • Zobrazit profil
    • E-mail
Re:Java DataInputStream - rychlost
« Odpověď #11 kdy: 19. 05. 2023, 21:43:32 »
a co otevirani souboru? co zkusit jine nacitaci (starsi) metody, a vyzkouset casy open, read, close atd. pripadne v c/c++ jak dlouho trva open souboru a pak prvni a dalsi ready.

Re:Java DataInputStream - rychlost
« Odpověď #12 kdy: 19. 05. 2023, 23:46:50 »
Pokud něco trvá 10s, tak je potřeba zjistit co. Zda jde o pasivní čekání, nebo o aktivní činnost, na úrovni systému zda jde o vytížení v userspace nebo v kernelu, zda IO nebo CPU. Začal bych tím, že bych to normálně profiloval, sampler v Java mission console navede na pár kliknutí na úzká hrdla, která lze pak lépe zkoumat. Ještě mě napadá zda nedochází k nějakému vylistování adresáře - pokud tam je mnoho souborů, může to trvat velmi dlouho.

Arthur

  • ***
  • 168
    • Zobrazit profil
    • E-mail
Re:Java DataInputStream - rychlost
« Odpověď #13 kdy: 21. 05. 2023, 10:58:07 »
Našel jsem řešení, tedy spíše workaround (příčinu stále neznám):

Namísto
Kód: [Vybrat]
DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));

float[] pole = new float[size];

for (int i = 0; i < size; i++)
  pole[i] = dis.readFloat();

Použít
Kód: [Vybrat]
byte[] bytes = new byte[4];
ByteBuffer buf = ByteBuffer.wrap(bytes);

for (int i = 0; i < size; i++) {
   dis.read(bytes);
   buf.rewind();
   pole[i] = buf.getFloat();
}


takže to zřejmě souvisí s implementací metod  readXxx() v DataInputStreamu, v tomto konkrétním případě DataInputStream.read(byte[] bytes) je lepší než DataInputStream.readFloat()

Re:Java DataInputStream - rychlost
« Odpověď #14 kdy: 21. 05. 2023, 11:28:24 »
Koukám do JDK:

DataInputStream.readFloat() volá interně DataInputStream.readInt, který 4x po sobě volá in.read() po bajtu, kde in je v tvém případě ten BufferedInputStream.

DataInputStream.read(bytes) to předává in.read(bytes), přičemž BufferInputStream.read(bytes, offset, len) volá svůj privátní read1(), která používá System.arraycopy().

Možná v tom je ten rozdíl.