<div dir="ltr">By &quot;selection&quot; I mean applying a selector a struct value. Sorry, a bit too terse, eh....<div><br></div><div>As for the result, that looks like some great things for us to put into benchmarks (or maybe the math library itself should be what we should look at).</div>
<div><br></div><div>I think that the reason the profiler isn&#39;t helping you is because the time you care about is being spent in the implementation of the primitives and that&#39;s visible only at the C level. If you can run gprof (or Apple&#39;s tools), you could probably see more of what&#39;s going on.<br>
<br>Overall, I feel like some of what you&#39;re asking has to do with what TR is doing and some with what&#39;s going on inside the procedure chaperones and so I&#39;m not sure the contract library itself is a place where fixes can happen. (But I didn&#39;t try to look into what exactly gets put into the chaperones by the contract library to be sure of that -- maybe there is something dumb happening there.)<br>
<br>Robby<br><br>On Fri, Dec 28, 2012 at 12:03 PM, Neil Toronto &lt;<a href="mailto:neil.toronto@gmail.com">neil.toronto@gmail.com</a>&gt; wrote:<br>&gt;<br>&gt; On 12/27/2012 06:21 PM, Robby Findler wrote:<br>&gt;&gt;<br>
&gt;&gt; One other place that we realized pretty quickly where we were wrong<br>&gt;&gt; about the boundaries and inefficiencies is struct declarations. It isn&#39;t<br>&gt;&gt; uncommon to put contracts on structs and put them in their own module<br>
&gt;&gt; (scribble does this). Currently the contract system can&#39;t tell that a<br>&gt;&gt; struct contract is actually on a struct and thus avoid checking when<br>&gt;&gt; doing a selection, but maybe that&#39;s somewhere that could help.<br>
&gt;<br>&gt;<br>&gt; What&#39;s a selection? Is it something I could understand by reading a certain paper?<br>&gt;<br>&gt; Because using arrays for examples is pretty abstract and carries a lot of baggage, I&#39;ve made some independent examples that demonstrate the same inefficiencies.<br>
&gt;<br>&gt; The first uses Typed Racket to generate contracts. The type ((Vectorof Index) -&gt; A) corresponds with the type (Array A), and is in fact the type of an array&#39;s procedure in `math/array&#39;.<br>&gt;<br>
&gt; In the following code, `make-array&#39; makes an unbounded-ish, constant &quot;array&quot;. The `array-touch&#39; function retrieves every element from 0 to n-1, and throws them away.<br>&gt;<br>&gt;<br>&gt; #lang racket<br>
&gt;<br>&gt; (module typed-defs typed/racket/base<br>&gt;   (require racket/fixnum racket/unsafe/ops)<br>&gt;   (provide make-array array-touch)<br>&gt;<br>&gt;   (: make-array (All (A) (A -&gt; ((Vectorof Index) -&gt; A))))<br>
&gt;   (define (make-array e) (lambda (js) e))<br>&gt;<br>&gt;   (: array-touch (All (A) (((Vectorof Index) -&gt; A) Index -&gt; Void)))<br>&gt;   (define (array-touch arr-proc n)<br>&gt;     (define: js : (Vectorof Index)  (vector 0))<br>
&gt;     (let: loop : Void ([i : Nonnegative-Fixnum  0])<br>&gt;       (when (i . fx&lt; . n)  ; proves i : Index<br>&gt;         (unsafe-vector-set! js 0 i)<br>&gt;         (arr-proc js)<br>&gt;         (loop (fx+ i 1)))))<br>
&gt;<br>&gt;   ;; Takes 20ms to call `arr-proc&#39; 2 million times<br>&gt;   (define arr-proc (make-array 10))<br>&gt;   (time (for ([_  (in-range 200)])<br>&gt;           (array-touch arr-proc 10000))))<br>&gt;<br>&gt; (require &#39;typed-defs)<br>
&gt;<br>&gt; ;; Takes 3300ms<br>&gt; (define arr-proc (make-array 10))<br>&gt; (time (for ([_  (in-range 200)])<br>&gt;         (array-touch arr-proc 10000)))<br>&gt;<br>&gt;<br>&gt; Summing up the numbers:<br>&gt;  * inside loop: 20ms<br>
&gt;  * outside loop: 3300ms<br>&gt;<br>&gt; These numbers correspond with what I get using arrays in untyped code, so this example probably isolates the main performance problems.<br>&gt;<br>&gt; Notice that `array-touch&#39; mutates the indexes vector it passes to `arr-proc&#39;. The array library also does this in its nested loops over elements to reduce allocation to one object (the mutated indexes vector). Still, it&#39;s worth checking out what would happen if immutable vectors were passed instead. If I&#39;m reading &quot;racket/contract/private/vector.rkt&quot; correctly, this could be faster because immutable vectors are checked immediately instead of getting wrapped in a chaperone.<br>
&gt;<br>&gt;   Changing (arr-proc js) to (arr-proc (vector-&gt;immutable-vector js)):<br>&gt;    * inside loop: 100ms<br>&gt;    * outside loop: 2950ms<br>&gt;<br>&gt; The &quot;inside loop&quot; timing is what I expected from my earlier experiments that motivated using mutable indexes. The &quot;outside loop&quot; timing suggests the chaperone isn&#39;t the biggest performance problem. (Of course, this example doesn&#39;t even look at the chaperoned value.)<br>
&gt;<br>&gt;   Changing (Vectorof Index) to (Vectorof Any): no change<br>&gt;<br>&gt; I think this is because the chaperone checks the contents of `js&#39; only when `js&#39; is indexed - which never happens in this example.<br>
&gt;<br>&gt;   Changing (Vectorof Index) to Any:<br>&gt;    * inside loop: 20ms (no change)<br>&gt;    * outside loop: 2200ms<br>&gt;<br>&gt; This suggests that putting any kind of contract on indexes is a big performance hit, but not the biggest.<br>
&gt;<br>&gt; There&#39;s not a lot left to investigate here without diving into TR&#39;s internals, so here&#39;s an untyped version that uses contracts:<br>&gt;<br>&gt;<br>&gt; #lang racket<br>&gt;<br>&gt; (module untyped-defs racket/base<br>
&gt;   (require racket/fixnum racket/unsafe/ops<br>&gt;            racket/contract (only-in typed/racket/base index?))<br>&gt;   (provide<br>&gt;    (contract-out<br>&gt;     [make-array  (-&gt; any/c (-&gt; (vectorof index?) any/c))]<br>
&gt;     [array-touch  (-&gt; (-&gt; (vectorof index?) any/c) index? void?)]))<br>&gt;<br>&gt;   (define (make-array e) (lambda (js) e))<br>&gt;<br>&gt;   (define (array-touch arr-proc n)<br>&gt;     (define js (vector 0))<br>
&gt;     (let loop ([i 0])<br>&gt;       (when (i . unsafe-fx&lt; . n)<br>&gt;         (unsafe-vector-set! js 0 i)<br>&gt;         (arr-proc js)<br>&gt;         (loop (unsafe-fx+ i 1)))))<br>&gt;<br>&gt;   (define arr-proc (make-array 10))<br>
&gt;   (time (for ([_  (in-range 200)])<br>&gt;           (array-touch arr-proc 10000))))<br>&gt;<br>&gt; (require &#39;untyped-defs)<br>&gt;<br>&gt; (define arr-proc (make-array 10))<br>&gt; (time (for ([_  (in-range 200)])<br>
&gt;         (array-touch arr-proc 10000)))<br>&gt;<br>&gt;<br>&gt; The timings for the loops in this version are identical to those from the typed version: 20ms inside vs. 3300ms outside. But this is surprising:<br>&gt;<br>
&gt;   Changing (vectorof index?) to any/c:<br>&gt;    * inside loop: 20ms<br>&gt;    * outside loop: 1700ms<br>&gt;<br>&gt; Doing the same thing in the typed version reduced the &quot;outside loop&quot; time to 2200ms, not 1700ms. It must not be the same thing after all. What&#39;s going on?<br>
&gt;<br>&gt;   Changing the &quot;array&quot; contract to (-&gt; (vectorof index?) any):<br>&gt;    * inside loop: 20ms<br>&gt;    * outside loop: 2340ms<br>&gt;<br>&gt; This suggests that checking for multiple values is pretty slow.<br>
&gt;<br>&gt;   Changing the &quot;array&quot; contract to (-&gt; any/c any):<br>&gt;    * inside loop: 20ms<br>&gt;    * outside loop: 20ms<br>&gt;<br>&gt; As expected; the procedure can be checked without wrapping.<br>&gt;<br>
&gt; FWIW, I would have used the profiler to do this, but it keeps being wrong about where time is spent. It&#39;s as if the contract wrappers don&#39;t exist. Is there a way to change that?<br>&gt;<br>&gt; Neil ⊥<br>&gt;<br>
</div></div>