images/logo.png
SHWB na blogspot  | Uživatel: Nepřihlášen

O programování 05 - Další varianty Javovské implementace foreach

V rámci předchozích dílů jsem zkoumal dvě varianty implementace foreach cyklu v Javě, konkrétně šlo o kopírování seznamu spojené s modifikací jednotlivých prvků. Porovnával jsem standardní cyklus foreach a jeho přímočarou funkcionální verzi z pohledu výkonnosti a přehlednosti. Jenom pro připomenutí, šlo o následující konstrukce pro foreach:



for (Integer value: inputList) { outputList.add(++value); }

resp. streamová varianta:



inputList.stream().forEach((value) -> { outputList.add(++value); });

Při prvním pohledu na streamovou variantu se mi nelíbí, že jde o metodu a ne koncept jazyka, což jsem rozebíral ve třetím díle.

Protože metod na objektu mohu napsat (teoreticky) kolik chci, tak máme ve standardní knihovně k dispozici další dvě varianty pro průchod všech prvků seznamu - přímo metodu listu list.forEach() a paralelní přístup přes streamy - list.parallelStream().forEach(). A s trochou snahy mohu napsat vlastní implementaci optimalizovanou pro mé potřeby. To je na jednu stranu super, ale jde to proti myšlence Javy jako vysokoúrovňového jazyka, který by se neměl zabývat podobnými detaily jako výběr konkrétní implementace.

Vraťme se však ke standardním metodám list.forEach()list.parallelStream().forEach(). Zápis zkoumaného algoritmu s využitím těchto metod vypadá následujícím způsobem:



inputList.forEach((integer) -> { outputList.add(++integer); });


inputList.parallelStream().forEach((integer) -> { outputList.add(++integer); });

Nabízí se samozřejmě otázka, jaký je mezi těmito čtyřmi implementacemi rozdíl. Foreach jako standardní a univerzální cyklus je jasný a ani parallelStream() mi nedělá problém - výpočet se spustí paralelně, tedy ve více vláknech (a na více jádrech). To může značně urychlit výpočet za cenu většího vytížení procesoru, ovšem pouze pro paralelizovatelné úlohy, což zdaleka nejsou všechny. Bohužel nelze omezit počet paralelních procesů, což by se hodilo, protože nevhodným použitím parallelStream() lze snadno zahltit systém, příkladů je plný stackoverflow.

Ale co forEach()stream().forEach? Na první pohled je na objektu Stream definováno mnohem více (funkcionálních) metod :). Ale teď vážně - podle dokuemntace je základní rozdíl v tom, že streamové forEach() je "terminal operation" a vstupním parametrem je "a non-interfering action to perform on the elements" zatímco listový foreach() má "action to perform on the elements" a je u něj garantované pořadí (podle iterátoru) provádění akcí. Klíčové je však "non-interfering action" související se změnou původních objektů, příslušnou diskusi ze stackowerflow shrnutou v Non-interference exact meaning in Java 8 streams se mi nechce překládat ani komentovat, myslím, že je jasná.

Každopádně pro zajímavost uvádím výsledky benchamrku pro všechny čtyři implementace.

Benchmark Mode Cnt Score Error Units
ListPlusPlus.forEach thrpt 5 6,725 ± 2,165 ops/ms
ListPlusPlus.parallelStream thrpt 5 3,880 ± 0,202 ops/ms
ListPlusPlus.standarForEach thrpt 5 8,374 ± 3,957 ops/ms
ListPlusPlus.streamForEach thrpt 5 8,567 ± 5,949 ops/ms
ListPlusPlus.forEach avgt 5 0,121 ± 0,059 ms/op
ListPlusPlus.parallelStream avgt 5 0,244 ± 0,026 ms/op
ListPlusPlus.standarForEach avgt 5 0,109 ± 0,018 ms/op
ListPlusPlus.streamForEach avgt 5 0,120 ± 0,075 ms/op
ListPlusPlus.forEach sample 38527 0,130 ± 0,001 ms/op
ListPlusPlus.parallelStream sample 19683 0,254 ± 0,003 ms/op
ListPlusPlus.standarForEach sample 41471 0,120 ± 0,003 ms/op
ListPlusPlus.streamForEach sample 39646 0,126 ± 0,001 ms/op
ListPlusPlus.forEach ss 5 0,513 ± 0,784 ms/op
ListPlusPlus.parallelStream ss 5 0,607 ± 0,285 ms/op
ListPlusPlus.standarForEach ss 5 1,430 ± 3,066 ms/op
ListPlusPlus.streamForEach ss 5 0,467 ± 0,894 ms/op

V tomto konkrétním případě nevidím žádný přínos paralelního přístupu, zřejmě není tento problém paralelizovatelný a navíc byl test spuštěn v jednom threadu, ale ani pět threadů situaci nějak dramaticky nezměnilo.

Benchmark Mode Cnt Score Error Units
ListPlusPlus.forEach thrpt 25 8,137 ± 0,781 ops/ms
ListPlusPlus.parallelStream thrpt 25 3,784 ± 0,127 ops/ms
ListPlusPlus.standarForEach thrpt 25 9,810 ± 1,187 ops/ms
ListPlusPlus.streamForEach thrpt 25 9,236 ± 0,298 ops/ms
ListPlusPlus.forEach avgt 25 0,115 ± 0,011 ms/op
ListPlusPlus.parallelStream avgt 25 0,254 ± 0,005 ms/op
ListPlusPlus.standarForEach avgt 25 0,093 ± 0,005 ms/op
ListPlusPlus.streamForEach avgt 25 0,127 ± 0,012 ms/op
ListPlusPlus.forEach sample 186409 0,134 ± 0,001 ms/op
ListPlusPlus.parallelStream sample 95608 0,262 ± 0,001 ms/op
ListPlusPlus.standarForEach sample 215093 0,116 ± 0,001 ms/op
ListPlusPlus.streamForEach sample 235593 0,106 ± 0,001 ms/op
ListPlusPlus.forEach ss 25 0,461 ± 0,149 ms/op
ListPlusPlus.parallelStream ss 25 0,710 ± 0,143 ms/op
ListPlusPlus.standarForEach ss 25 1,033 ± 0,463 ms/op
ListPlusPlus.streamForEach ss 25 0,423 ± 0,144 ms/op

Pro ověření přínosu paralelního přístupu s využitím parallelStream() zkusím někdy příště najít vhodnější úlohu - třeba násobení matic :).

02.01.2017
Přidat názor:
Vyhrazuji si právo libovolný komentář smazat bez udání důvodu. Kritika mi nevadí, ale chci omezit anonymní výkřiky, které nemají s tématem nic společného.
V textu je možné používat HTML tagy a tuto zjednodušenou MarkDown syntaxi
Jméno
Text
Postřehy:
31.07.2019: Arduino 01 - Motivace k elektrotechnice
To jsem se jednou, nechci říct nudil, ale zkrátka jsem narazil na knihy "Porty, bajty, osmibity" a "Hradla, volty, jednočipy" od Martina Malého z produkce sdružení NIC.CZ, které jsou volně dostupné na knihy.nic.cz.
extravaganza.controverso@seznam.cz: Zdravím, krásný a informacemi nabitý blog. Musím pochválit. Plánuji rozjet undergroundový zin, co by se týkal black matalu, ambientu, satanismu, left hand
Poslední diskuse Postřehy
O programování 06 - Návrhové vzory - síla i slabina Javy
P.S. samozrejme "Context" mel byt "Client" .. To jsem jen narazil na nejak divne pojmenovany diagram.. (Context je samozrejme trosku neco jineho...)
...
David | 25.02.2017
O programování 06 - Návrhové vzory - síla i slabina Javy
To k cemu jsi dosel (tedy implementace LooperRunner + ILoopMethod), tak je ta prava Strategy by GoF :) To co je tam dulezite je ze Context (LooperRunner) je oddeleny od Strategy (ILoopMethod),
...
David | 25.02.2017
O programování 03 - Přehlednost funkcionálního zápisu v Java 8
Máš pravdu, to map je tam zbytečné. Odněkud jsem to opsal a nezkontroloval. Tím ovšem trochu padá pointa celého článku.
...
Saha | 14.12.2016
O programování 03 - Přehlednost funkcionálního zápisu v Java 8
Jen takova otazka k tomu druhemu prikladu:
Proc tam tu cast s "map" ktera de facto s prvky toho streamu nic nedela? Nestacilo by
list.stream().reduce(0, Integer::sum);
?
Ja teda moc
...
David P. | 13.12.2016
Paleo na půl - 01 - První tři dny bez mléka
Kvalitní hořké čokolády jsou bez mléka.. :) (a to i ty méně "kvalitní"). Mléko bývá součástí jen těch "sladkých".
...
David | 04.05.2015
Statistiky
Aktualizováno: 29.09.2019
Počet článků/fotek: 1374/13846
(C) Saha - 1990 - 2019 - Verze 1.3.32 - 11.05.2019 - Generated by SHREC 2.216
Veškeré zde uvedené materiály vyjadřují pouze moje soukromé názory (s výjimkou knihy návštěv a diskusí, kam může přispívat kdokoliv), a pokud s nimi někdo nesouhlasí, tak je to jeho problém, nikoliv můj.