<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">Dear Marc,</div><div class="gmail_quote"><br></div><div class="gmail_quote">Thanks for all your clarifications.</div><div class="gmail_quote"><br></div><div class="gmail_quote">I think I gathered from you that actually auto-forcing fundamentally is a very expensive problem to solve and that for this reason for me to solve my particular problem, I should minimize the amount of Gambit primitives that need to auto-force, to a minimum.</div><div class="gmail_quote"><br></div><div class="gmail_quote">And indeed yes all with you that if such a simple reduction of the problem would not be possible, then dataflow analysis would be a fit.</div><div class="gmail_quote"><br></div><div class="gmail_quote"><br></div><div class="gmail_quote">For my final clarity on the implications of this problem, below I'd like to ask you additionally briefly about how forcing and the auto-forcing transformation actually work, and also check if forcing via protected virtual memory could be any good idea ever -</div><div class="gmail_quote"><br></div><div class="gmail_quote"><br></div><div class="gmail_quote">2017-09-19 3:28 GMT+08:00 Marc Feeley <span dir="ltr"><<a href="mailto:feeley@iro.umontreal.ca" target="_blank">feeley@iro.umontreal.ca</a>></span>:<br><div>[..]</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">> Wait, what does |##apply-with-procedure-check| actually do, in what situations is it invoked, is this run on all (f a1 a2 ...) with oper = f and args = (list a1 a2 ...) for any procedure call made anywhere, when compiling with (declare (safe))?<br></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><span class="gmail-">
<br>
</span>No… In the scope of a (declare (safe)) the generated C code will check if the “operator” position, the f here, is a procedure.  A direct transfer of control to f is done when f is a procedure.  The function ##apply-with-procedure-check is tail called by the runtime system when (##procedure? oper) is #f.</blockquote><div><br></div><div>Ah I understand - right, so auto-forcing has zero overhead for operators in procedure calls. Great!</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><span class="gmail-">> Will test and benchmark following your next clarification.<br>
><br>
><br>
> Thanks a lot!<br>
<br>
</span>I don’t understand why you are so concerned with this issue (forcing the operator position of a call)… The real overhead is auto-forcing data-structures… A good approach to minimize the overhead is a dataflow analysis or even BBV…<br></blockquote><div><br></div><div><br></div><div>I made a preliminary test of --enable-auto-forcing's overhead and it suggested that --enable-auto-forcing out of the box incurs something like 400% overhead, on digest.scm , which is indeed a quite unfair example.</div><div><br></div><div>While not a pedantic approach on my behalf, I went into asking the question, how make auto-forcing faster, and came up with the idea that removing operator forcing could help speed things up.</div><div><br></div><div>Now you clarified that operator forcing actually has zero overhead - thanks.</div><div><br></div><div><br></div><div>I need to verify the 400% overhead figure, but, where is most of the overhead from auto-forcing caused?</div><div><br></div><div><br>Is it that (##force), which is done by macro-force-vars on every single value in the system, at every evaluation point (<a href="https://github.com/gambit/gambit/blob/29103e6a29b8fbbf7d6fc772a344b814be3f1c1a/lib/_gambit%23.scm#L492">https://github.com/gambit/gambit/blob/29103e6a29b8fbbf7d6fc772a344b814be3f1c1a/lib/_gambit%23.scm#L492</a>), has an inherent overhead in that an extra variable slot is added, and a type check, a comparison, and a conditional jump?</div><div><br></div><div><br></div><div>I can't find ##force's code anywhere, so it appears to me that it's a product of the compiler and is inlined. I presume ##force's pseudocode would look something like</div><div><br></div><div><font face="monospace, monospace">(define-prim (##force value)</font></div><div><font face="monospace, monospace">  (if (##promise? value)</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    (begin</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">      (if (not (##promise-value-slot-set? value))</font></div><div><font face="monospace, monospace">        (##promise-value-slot-set! value (##promise-thunk-for-promise-code value)))</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">      (</font><span style="font-family:monospace,monospace">##promise-value-slot</span><font face="monospace, monospace"> value))</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">    value))</font></div><div><br></div><div>, and its application in auto-forcing is a transformation something like</div><div><br></div><div><font face="monospace, monospace">(define (language-primitive op .. arg .. ) ..logics..)</font></div><div><br></div><div>to</div><div><br></div><div><font face="monospace, monospace">(define (language-primitive op .. arg .. ) (let ((arg (##force arg))) ..logics..))</font><br></div><div><br></div><div>.</div><div><br></div><div>I guess in this light, this alternative transformation would not be of any particular use:</div><div><br></div><div><div><font face="monospace, monospace">(define (language-primitive op .. arg .. ) (set! arg (##force arg)) ..logics..)</font><br></div></div><div><font face="monospace, monospace"><br></font></div><div><br></div><div>An exotic idea would be to use protected virtual memory like described here <a href="https://medium.com/@MartinCracauer/generational-garbage-collection-write-barriers-write-protection-and-userfaultfd-2-8b0e796b8f7f">https://medium.com/@MartinCracauer/generational-garbage-collection-write-barriers-write-protection-and-userfaultfd-2-8b0e796b8f7f</a> . I guess probably this would not work out at all, but I would like to ask you about it briefly anyhow -</div><div><br></div><div>For it to be useful globally, Brooks/forwarding pointers would need to be enabled in Gambit (so normally the forwarding pointer would be a self-reference, whereas for promises they would be located in the protected memory, which would spark a SIGSEGV, used as a trigger to run the promise code and update pointers).</div><div><br></div><div>For a limited use situation where the promise code's result type and size are pre-known, virtual memory for the promise value could be pre-allocated, and the SIGSEGV handler would function to spark the evaluation which would lead to filling out those protected memory addresses with real values.<br></div><div><br></div><div>However - I doubt that the SIGSEGV handler could be made easily to interact with the Scheme world though!</div><div><br></div><div>That is, the SIGSEGV handler would cause a trampoline jump to the promise code, and, at completion of the promise code, storing away its result and continuing at the Scheme code location that trigged the SIGSEGV.</div><div><br></div><div>I guess this would be difficult or impossible, because we don't know exactly what location in the C code trigged the SIGSEGV, and so the GVM would maybe not have operational integrity so that a trampoline in the Scheme world could take place -</div><div><br></div><div>I guess this is so far-out that it should not even be considered, what do you say?</div><div><br></div><div><br></div><div><br></div><div>So then, right, code analysis would help.</div><div><br></div><div>Also maybe the most simple speedup would be gained from reducing the forcing to need to take place only at a very limited subset of primitives.</div><div><br></div><div><br></div><div>Thanks,</div><div>Adam</div></div></div></div>