Re: Better RecursiveTask Example
Alex Otenko via Concurrency-interest <[email protected]> Thu, 25 Nov 2021 00:17:35 +0000
| Newsgroups | gmane.comp.java.jsr.166-concurrency |
|---|---|
| Message-ID | <CANkgWKhmOsMQKRPaTt2MJt7Hjr2Eg3O8dOPZYarXiJ=Fk-+JAQ@mail.gmail.com> |
--===============5668002650460905087== Content-Type: multipart/alternative; boundary="0000000000007bf81905d191e7e8" --0000000000007bf81905d191e7e8 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable https://bit.ly/3HVgEey - but maybe this is less silly, as we can actually make subcomputations wait for the previous task to complete. Alex On Wed, 24 Nov 2021, 23:30 Alex Otenko, <[email protected]> wrote: > I presume logarithmic cost Fibonacci is not considered, because there's > little point doing it recursively? (Although can still show off parallel > computations) > > https://bit.ly/3oVFeTD > > > Alex > > On Wed, 24 Nov 2021, 19:19 Dr Heinz M. Kabutz via Concurrency-interest, < > [email protected]> wrote: > >> Every time I see the example in RecursiveTask I have to cringe: >> >> >> https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/c= oncurrent/RecursiveTask.html >> >> For a classic example, here is a task computing Fibonacci numbers: >> >> >> class Fibonacci extends RecursiveTask<Integer> { >> final int n; >> Fibonacci(int n) { this.n =3D n; } >> protected Integer compute() { >> if (n <=3D 1) >> return n; >> Fibonacci f1 =3D new Fibonacci(n - 1); >> f1.fork(); >> Fibonacci f2 =3D new Fibonacci(n - 2); >> return f2.compute() + f1.join(); >> } >> } >> However, besides being a dumb way to compute Fibonacci functions (there >> is a simple fast linear algorithm that you'd use in practice), this is >> likely to perform poorly because the smallest subtasks are too small to >> be worthwhile splitting up. Instead, as is the case for nearly all >> fork/join applications, you'd pick some minimum granularity size (for >> example 10 here) for which you always sequentially solve rather than >> subdividing. >> >> >> >> Indeed, it is a dumb way to compute Fibonacci, but the "fast linear" >> algorithm isn't fast either. Since we overflow even Long after about >> fibonacci(90), we would need BigInteger. And there the add is linear, >> meaning that the "fast linear" algorithm referred to here is probably >> going to end up as "slow quadratic". >> >> To me, this example sends the completely wrong message. Let's take the >> worst possible algorithm and parallelize it. Great. That means if we use >> 1000 processors, we can solve the problem of n+10 in the same time as n >> with a single processor. >> >> I do realize this is meant to illustrate a point, but it doesn't do it >> very well IME. I would like to propose to change this to a slightly >> better example, for example a Factorial calculation: >> >> public class FactorialTask extends RecursiveTask<BigInteger> { >> private final int from, to; >> >> public FactorialTask(int n) { >> this(0, n); >> } >> >> private FactorialTask(int from, int to) { >> this.from =3D from; >> this.to =3D to; >> } >> >> protected BigInteger compute() { >> if (from =3D=3D to) { >> if (from =3D=3D 0) return BigInteger.ONE; >> return BigInteger.valueOf(from); >> } >> int mid =3D (from + to) >>> 1; >> FactorialTask leftTask =3D new FactorialTask(from, mid); >> FactorialTask rightTask =3D new FactorialTask(mid + 1, to); >> leftTask.fork(); >> BigInteger right =3D rightTask.invoke(); >> BigInteger left =3D leftTask.join(); >> return left.multiply(right); >> } >> } >> >> This is actually a *lot* faster than the stream version: >> >> public static BigInteger factorialStream(int n) { >> return IntStream.rangeClosed(1, n) >> .mapToObj(BigInteger::valueOf) >> .reduce(BigInteger.ONE, BigInteger::multiply); >> } >> >> (this has to do more with the algorithms used by BigInteger's multiply >> method than the parallelization, but that also has an effect. >> >> >> Alternatively, if we have to have Fibonacci, could we at least change it >> to Dijkstra's Sum of Squares? I believe there are slightly better >> algorithms, but this one works very nicely with parallelisation: >> >> public class FibonacciTask extends RecursiveTask<BigInteger> { >> private final int n; >> >> public FibonacciTask(int n) { >> this.n =3D n; >> } >> >> @Override >> protected BigInteger compute() { >> return switch (n) { >> case 0 -> BigInteger.ZERO; >> case 1 -> BigInteger.ONE; >> default -> { >> // Dijkstra's Sum of Squares Algorithm >> int half =3D (n + 1) / 2; >> FibonacciTask f0_task =3D new FibonacciTask(half - 1); >> f0_task.fork(); >> FibonacciTask f1_task =3D new FibonacciTask(half); >> BigInteger f1 =3D f1_task.invoke(); >> BigInteger f0 =3D f0_task.join(); >> >> if (n % 2 =3D=3D 1) { >> yield f0.multiply(f0).add(f1.multiply(f1)); >> } else { >> yield f0.shiftLeft(1).add(f1).multiply(f1); >> } >> } >> }; >> } >> } >> >> Please let me know if you agree with this change (or propose a different >> example). I would be happy to make the change. I presume it would need >> to be done in the CVS? Or can I do it in the OpenJDK GitHub repository >> and then we can sync that over to CVS? (My preference would be GitHub) >> >> >> >> >> Regards >> >> Heinz >> -- >> Dr Heinz M. Kabutz (PhD CompSci) >> Author of "The Java=E2=84=A2 Specialists' Newsletter" - www.javaspeciali= sts.eu >> Java Champion - www.javachampions.org >> JavaOne Rock Star Speaker >> Tel: +30 69 75 595 262 >> Skype: kabutz >> >> _______________________________________________ >> Concurrency-interest mailing list >> [email protected] >> http://cs.oswego.edu/mailman/listinfo/concurrency-interest >> > --0000000000007bf81905d191e7e8 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable <div dir=3D"auto"><a href=3D"https://bit.ly/3HVgEey">https://bit.ly/3HVgEey= </a> - but maybe this is less silly, as we can actually make subcomputation= s wait for the previous task to complete.<div dir=3D"auto"><br></div><div d= ir=3D"auto">Alex</div></div><br><div class=3D"gmail_quote"><div dir=3D"ltr"= class=3D"gmail_attr">On Wed, 24 Nov 2021, 23:30 Alex Otenko, <<a href= =3D"mailto:[email protected]">[email protected]</a>> w= rote:<br></div><blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .8ex= ;border-left:1px #ccc solid;padding-left:1ex"><div dir=3D"auto">I presume l= ogarithmic cost Fibonacci is not considered, because there's little poi= nt doing it recursively? (Although can still show off parallel computations= )<div dir=3D"auto"><br></div><div dir=3D"auto"><a href=3D"https://bit.ly/3o= VFeTD" target=3D"_blank" rel=3D"noreferrer">https://bit.ly/3oVFeTD</a></div= ><div dir=3D"auto"><br></div><div dir=3D"auto"><br></div><div dir=3D"auto">= Alex</div></div><br><div class=3D"gmail_quote"><div dir=3D"ltr" class=3D"gm= ail_attr">On Wed, 24 Nov 2021, 19:19 Dr Heinz M. Kabutz via Concurrency-int= erest, <<a href=3D"mailto:[email protected]" target=3D"= _blank" rel=3D"noreferrer">[email protected]</a>> wrote= :<br></div><blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .8ex;bor= der-left:1px #ccc solid;padding-left:1ex">Every time I see the example in R= ecursiveTask I have to cringe:<br> <br> <a href=3D"https://docs.oracle.com/en/java/javase/11/docs/api/java.base/jav= a/util/concurrent/RecursiveTask.html" rel=3D"noreferrer noreferrer noreferr= er" target=3D"_blank">https://docs.oracle.com/en/java/javase/11/docs/api/ja= va.base/java/util/concurrent/RecursiveTask.html</a><br> <br> For a classic example, here is a task computing Fibonacci numbers:<br> <br> <br> =C2=A0=C2=A0class Fibonacci extends RecursiveTask<Integer> {<br> =C2=A0=C2=A0=C2=A0 final int n;<br> =C2=A0=C2=A0=C2=A0 Fibonacci(int n) { this.n =3D n; }<br> =C2=A0=C2=A0=C2=A0 protected Integer compute() {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 if (n <=3D 1)<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return n;<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 Fibonacci f1 =3D new Fibonacci(n - 1);<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 f1.fork();<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 Fibonacci f2 =3D new Fibonacci(n - 2);<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return f2.compute() + f1.join();<br> =C2=A0=C2=A0=C2=A0 }<br> =C2=A0=C2=A0}<br> However, besides being a dumb way to compute Fibonacci functions (there <br= > is a simple fast linear algorithm that you'd use in practice), this is = <br> likely to perform poorly because the smallest subtasks are too small to <br= > be worthwhile splitting up. Instead, as is the case for nearly all <br> fork/join applications, you'd pick some minimum granularity size (for <= br> example 10 here) for which you always sequentially solve rather than <br> subdividing.<br> <br> <br> <br> Indeed, it is a dumb way to compute Fibonacci, but the "fast linear&qu= ot; <br> algorithm isn't fast either. Since we overflow even Long after about <b= r> fibonacci(90), we would need BigInteger. And there the add is linear, <br> meaning that the "fast linear" algorithm referred to here is prob= ably <br> going to end up as "slow quadratic".<br> <br> To me, this example sends the completely wrong message. Let's take the = <br> worst possible algorithm and parallelize it. Great. That means if we use <b= r> 1000 processors, we can solve the problem of n+10 in the same time as n <br= > with a single processor.<br> <br> I do realize this is meant to illustrate a point, but it doesn't do it = <br> very well IME. I would like to propose to change this to a slightly <br> better example, for example a Factorial calculation:<br> <br> public class FactorialTask extends RecursiveTask<BigInteger> {<br> =C2=A0=C2=A0=C2=A0=C2=A0 private final int from, to;<br> <br> =C2=A0=C2=A0=C2=A0=C2=A0 public FactorialTask(int n) {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 this(0, n);<br> =C2=A0=C2=A0=C2=A0=C2=A0 }<br> <br> =C2=A0=C2=A0=C2=A0=C2=A0 private FactorialTask(int from, int to) {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 this.from =3D from;<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 <a href=3D"http://this.to"= rel=3D"noreferrer noreferrer noreferrer" target=3D"_blank">this.to</a> =3D= to;<br> =C2=A0=C2=A0=C2=A0=C2=A0 }<br> <br> =C2=A0=C2=A0=C2=A0=C2=A0 protected BigInteger compute() {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 if (from =3D=3D to) {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 if= (from =3D=3D 0) return BigInteger.ONE;<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 re= turn BigInteger.valueOf(from);<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 }<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 int mid =3D (from + to) &g= t;>> 1;<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 FactorialTask leftTask =3D= new FactorialTask(from, mid);<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 FactorialTask rightTask = =3D new FactorialTask(mid + 1, to);<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 leftTask.fork();<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 BigInteger right =3D right= Task.invoke();<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 BigInteger left =3D leftTa= sk.join();<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return left.multiply(right= );<br> =C2=A0=C2=A0=C2=A0=C2=A0 }<br> }<br> <br> This is actually a *lot* faster than the stream version:<br> <br> =C2=A0=C2=A0=C2=A0=C2=A0 public static BigInteger factorialStream(int n) {<= br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return IntStream.rangeClos= ed(1, n)<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 .mapToObj(BigInteger::valueOf)<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 .reduce(BigInteger.ONE, BigInteger::multiply);<br> =C2=A0=C2=A0=C2=A0=C2=A0 }<br> <br> (this has to do more with the algorithms used by BigInteger's multiply = <br> method than the parallelization, but that also has an effect.<br> <br> <br> Alternatively, if we have to have Fibonacci, could we at least change it <b= r> to Dijkstra's Sum of Squares? I believe there are slightly better <br> algorithms, but this one works very nicely with parallelisation:<br> <br> public class FibonacciTask extends RecursiveTask<BigInteger> {<br> =C2=A0=C2=A0=C2=A0=C2=A0 private final int n;<br> <br> =C2=A0=C2=A0=C2=A0=C2=A0 public FibonacciTask(int n) {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 this.n =3D n;<br> =C2=A0=C2=A0=C2=A0=C2=A0 }<br> <br> =C2=A0=C2=A0=C2=A0=C2=A0 @Override<br> =C2=A0=C2=A0=C2=A0=C2=A0 protected BigInteger compute() {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return switch (n) {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 ca= se 0 -> BigInteger.ZERO;<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 ca= se 1 -> BigInteger.ONE;<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 de= fault -> {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 // Dijkstra's Sum of Squares Algorithm<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 int half =3D (n + 1) / 2;<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 FibonacciTask f0_task =3D new FibonacciTask(half - 1)= ;<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 f0_task.fork();<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 FibonacciTask f1_task =3D new FibonacciTask(half);<br= > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 BigInteger f1 =3D f1_task.invoke();<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 BigInteger f0 =3D f0_task.join();<br> <br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 if (n % 2 =3D=3D 1) {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 yield f0.multiply(f0).add(f1.= multiply(f1));<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 } else {<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 yield f0.shiftLeft(1).add(f1)= .multiply(f1);<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0 }<br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 }<= br> =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 };<br> =C2=A0=C2=A0=C2=A0=C2=A0 }<br> }<br> <br> Please let me know if you agree with this change (or propose a different <b= r> example). I would be happy to make the change. I presume it would need <br> to be done in the CVS? Or can I do it in the OpenJDK GitHub repository <br> and then we can sync that over to CVS? (My preference would be GitHub)<br> <br> <br> <br> <br> Regards<br> <br> Heinz<br> -- <br> Dr Heinz M. Kabutz (PhD CompSci)<br> Author of "The Java=E2=84=A2 Specialists' Newsletter" - <a hr= ef=3D"http://www.javaspecialists.eu" rel=3D"noreferrer noreferrer noreferre= r" target=3D"_blank">www.javaspecialists.eu</a><br> Java Champion - <a href=3D"http://www.javachampions.org" rel=3D"noreferrer = noreferrer noreferrer" target=3D"_blank">www.javachampions.org</a><br> JavaOne Rock Star Speaker<br> Tel: +30 69 75 595 262<br> Skype: kabutz<br> <br> _______________________________________________<br> Concurrency-interest mailing list<br> <a href=3D"mailto:[email protected]" rel=3D"noreferrer nor= eferrer" target=3D"_blank">[email protected]</a><br> <a href=3D"http://cs.oswego.edu/mailman/listinfo/concurrency-interest" rel= =3D"noreferrer noreferrer noreferrer" target=3D"_blank">http://cs.oswego.ed= u/mailman/listinfo/concurrency-interest</a><br> </blockquote></div> </blockquote></div> --0000000000007bf81905d191e7e8-- --===============5668002650460905087== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ Concurrency-interest mailing list [email protected] http://cs.oswego.edu/mailman/listinfo/concurrency-interest --===============5668002650460905087==--