On Wednesday, 4 January 2017 21:05:58 UTC+7, Bob Zhang wrote:
>
> Hi Gordon, 
>   As you can see, BuckleScript already did a very good good job here, of 
> course, there are still places for improvement. To write extremely high 
> performance code, you should avoid creating a closure in a tight loop, here 
> you can lift the `nxtc` into the top level, in the future, we will do this 
> inside the compiler. Lets take further discussions off the list -- Hongbo
>

Hi Hongbo, yes, BuckleScript did a very good job other than 1) mistakenly 
determining that the closure was necessary, then 2) not automatically 
lifting the closure, and then 3) not inlining the resulting (non-closure) 
function call away (see Haskell join points optimizations).  I'm just 
sayin' ;)  In case you needed adding to your TODO list...

In fact, BuckleScript *already does* catch most cases of these, and didn't 
in this one case because the enclosing loop has been tail call optimized 
(manually in case of the code I showed) so become a "ugly imperative code" 
loop internally, and the compiler then treats the binding `p` derived from 
an impure loop variable as impure and thus needing closure treatment, which 
seems to cause the resulting function to be invalidated as a candidate for 
inlining.  The compiler needs to see that the the binding `p` is invariant 
(in fact guaranteed as the compiler injected a let on the loop variable) 
during the whole creation and execution of the `nxtc` function, therefore 
doesn't need the closure and thus can be inlinee.  (BTW, it seems that 
BuckleScript ignores the [@@inline] and [@inlined] OCaml extensions?)

If  the `nxtc` function is lifted to the top level, then it needs to be 
written so that the otherwise free bindings are arguments (`p` and `cmpsts` 
in this case) and thus it is no longer a closure, and even if there were an 
inner worker closure, BuckleScript already inlines and tail calls when 
captured bindings are arguments or derived from arguments of a function 
call, recognizing that they are pure.  There will still be a function call 
to the lifted `nxtc` where there wouldn't be if it were inlined however, 
which means that inlining would be better, although the function call has 
negligible cost relative to the rest of the work in this case.

The only reason this was noticeable here is that it turns out that the time 
cost is not so much in the creation of the closure but much more the cost 
of binding a function (a closure in this case) to a variable.  It's 
interesting how incredibly slow JavaScript/V8 is at creating a function 
binding rather than just calling one - I calculate 10's of thousands of CPU 
clock cycles; it must be dropping back to evaluating the expression each 
loop rather than JIT compiling it plus the expected time of creating a 
function object on the heap.  This isn't BuckleScript's problem, just that 
BuckleScript can help avoid it for the unwary.

Stepping back, I don't have much to complain about, as before I met 
BuckleScript I would have been writing TypeScript/JavaScript mostly 
imperatively and BuckleScript/OCaml offers the option to do the same, if 
necessary for extremely time critical code!  I've just gotten used to 
avoiding that paradigm.

Yes, let's move the discussion somewhere else as it isn't relevant here. I 
just want to say thanks for the heads-up on BuckleScript, which I am quite 
enjoying in spite of its OCaml syntax foibles.  Now I can develop both Elm 
and BuckleScript inside similar development environments using the Visual 
Studio Code plugins. - Gordon
  

> On Wed, Jan 4, 2017 at 1:17 AM, GordonBGood <gordo...@gmail.com 
> <javascript:>> wrote:
>
>> Hi Bob/Hongbo,
>>
>> I've run into my first speed problem with BuckleScript:  When it decides 
>> it needs to form a closure of captured free binding(s), it creates a 
>> "function returning a function" with the outer function's arguments being 
>> the current state of the "pure" captured bindings, and the inner returned 
>> function the actual closure code.  When this happens anywhere near an inner 
>> loop, the code gets very slow, although sometimes the compiler is smart 
>> enough to infer that the closure is not necessary is when the binding value 
>> is an argument of an enclosing function; however, this does not happen for 
>> local `let` bindings even when they are in the same scope as the 
>> callee/caller sites.
>>
>> For example, the following bit-packed Sieve of Eratosthenes sieves over a 
>> range and returns the bit packed array of found primes over that range 
>> (odds-only.  It runs about five times slower than using imperative code 
>> for/while loops (top of a quarter million).  Code is as follows:
>>
>> let soe_loop top =
>>   if top < 3 then Array.make 0 0 else
>>   let ndxlmt = (top - 3) lsr 1 in
>>   let cmpsts = Array.make ((ndxlmt lsr 5) + 1) 0 in
>>   for loop = 1 to 1000 do (* do it many times for timing *)
>>     let pir = ref 0 in
>>     while !pir <= ndxlmt do
>>       let pi = !pir in
>>       let p = pi + pi + 3 in
>>       let rec nxtc ci =
>>       if ci > ndxlmt then () else
>>       let w = ci lsr 5 in
>>       cmpsts.(w) <- cmpsts.(w) lor (1 lsl (ci land 31));
>>       nxtc (ci + p) in
>>       let si = (p * p - 3) lsr 1 in
>>       if si > ndxlmt then pir := ndxlmt + 1 else (
>>         if cmpsts.(pi lsr 5) land (1 lsl (pi land 31)) == 0 then
>>           nxtc si
>>   cmpsts
>>
>> where the outer `pi` loops are imperative and the inner `nxtc` loop is 
>> functional; this latter becomes the closure that captures the `p` value as 
>> you can see in the following generated JavaScript:
>>
>> function soe_loop(top) {
>>   if (top < 3) {
>>     return Caml_array.caml_make_vect(0, 0);
>>   }
>>   else {
>>     var ndxlmt = ((top - 3 | 0) >>> 1);
>>     var cmpsts = Caml_array.caml_make_vect((ndxlmt >>> 5) + 1 | 0, 0);
>>     for(var loop = 1; loop <= 1000; ++loop){
>>       var pir = 0;
>>       while(pir <= ndxlmt) {
>>         var pi = pir;
>>         var p = (pi + pi | 0) + 3 | 0;
>>         var nxtc = (function(p){
>>         return function nxtc(_ci) {
>>           while(true) {
>>             var ci = _ci;
>>             if (ci > ndxlmt) {
>>               return /* () */0;
>>             }
>>             else {
>>               var w = (ci >>> 5);
>>               cmpsts[w] = cmpsts[w] | (1 << (ci & 31));
>>               _ci = ci + p | 0<span style="color:#660" 
>> class="m_-2806577415036512984styled-by-p
>>
>

-- 
You received this message because you are subscribed to the Google Groups "Elm 
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to elm-discuss+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to