Re: Discovering which optimizations took place

Aggelos Biboudis <[email protected]>
Newsgroups gmane.comp.lang.ml.mlton.user
Message-ID <CAHLSyCmmP5QFN8KbV4fY+80YDFdH_wmFVCRw66+Vwa6Bx6iuwA@mail.gmail.com>
Hi Matthew,

At first, I should thank you for the elaborate answer and the way you
describe, is way more effective than trying naively to figure out things
with .s directly. As I did at first.

As a first step I will try to familiarize myself with the -keep-pass flag.

Thx for the tip with the ordering of .pre vs .post! This will save me some
time!

The name "gen" comes from your
> "Streams.ofArray" implementation and the "Stream.sum",
> "Stream.flatMap", and "Stream.map" code has been inlined and is being
> driven by the source stream --- which I think is precisely what you
> want for a push-based stream library.
>

This is exactly what I was looking for. I will follow the same strategy
with the multiple filters and maps to see what happens there.

And just to clear things up (for me): This is the part that helped me to
see that pattern matching over environments will result in gen_0 only
through the flatMap argument deconstruction (v2). SSA svg really helps!


​
Kind regards and thank u,
Aggelos Biboudis.


On Wed, Nov 12, 2014 at 10:37 PM, Matthew Fluet <[email protected]>
wrote:

> On Wed, Nov 12, 2014 at 8:17 AM, Aggelos Biboudis <[email protected]>
> wrote:
> > recently I developed/ported to SML, a push-based stream processing
> library
> > just to investigate the performance characteristics of such a design
> under
> > MLton. It was an opportunity to learn SML (sry in advance if you notice
> > unidiomatic code) and use MLton for the first time (at last). I was
> amazed
> > by the result, but I am more interested to learn about the optimizations
> > that took place in this particular scenario.
>
> Glad to hear that MLton performed positively with your streaming library.
>
> > For example, in the flatMap/Map case (Cartesian product) did I
> essentially
> > get nested loops?
> >
> > Can you provide me guidelines to further analyze the underlying code, of
> > such an experiment?
> > Which is the preferred way? x86 deep dive or AST-pretty printing somehow?
>
> Generally, most of the significant optimizations are performed on the
> SSA and SSA2 intermediate languages.  You can read more about the ILs
> and the various optimizations passes at:
>   http://mlton.org/SSA
>   http://mlton.org/SSASimplify
>   http://mlton.org/SSA2
>   http://mlton.org/SSA2Simplify
>
> You can keep the various intermediate languages with the "-keep <IL>"
> command-line flag.  For instance:
>   mlton -verbose 2 -keep core-ml -keep xml -keep sxml -keep ssa -keep
> ssa2 -keep rssa -keep machine -keep g
> will dump the program in each of the major intermediate languages (see
> http://mlton.org/IntermediateLanguage) as well as the generated
> assembly ("-keep g").
>
> You can also keep the IL of the program before and after individual
> optimizations with the "-keep-pass <RE>" command-line flag.  For
> instance:
>   mlton -verbose 2 -keep-pass introduceLoops1
> will dump the program before and after the first "introduceLoops"
> optimization pass.  One word of warning when looking at the .pre.ssa
> vs. the .post.ssa files: many of the SSA optimizations effectively
> rewrite the program using a "rev-map" over the collection of
> functions; this is because the order of functions in the program IL
> does not matter and a rev-map is tail-recursive (whereas a map is
> either not tail-recursive or performs an extra reverse).  However, the
> consequence is that a textual diff of .pre.ssa vs. .post.ssa is often
> not particularly helpful to identify what changed.
>
> The "-keep-pass <RE>" command-line flag takes a regular-expression, so
> "-keep-pass '.*'" will effectively dump the program before and after
> every optimization and translation pass.
>
> For the SSA and SSA2 ILs, it is often convenient to see the
> control-flow graph representation of the individual functions; the
> "-keep dot" command-line flag causes any dumped SSA/SSA2/RSSA IL
> program to also dump Graphviz .dot files for the interprocedural
> call-graph and for each of the individual functions.  Sometimes these
> control-flow graphs can be fairly large, but I've had good luck using
> Graphiviz's dot tool to convert them to SVG format and viewing them in
> a recent web-browser.
>
> The "-drop-pass <RE>" command-line flag cab be used to skip a
> particular optimization pass.  This can be useful for seeing the
> impact of a particular optimization pass on a benchmark.  Of course,
> it isn't always easy to isolate the benefit of individual passes;
> often, the benefit is that it enables other optimization passes.
>
>
> With regards to your specific benchmark program, I did the following:
>   make FLAGS="-verbose 2 -keep ssa -keep dot" bench
>   dot -Tsvg bench-streams.ssa.gen_0.cfg.dot >
> bench-streams.ssa.gen_0.cfg.dot.svg
> I believe that the "gen_0" SSA IL function corresponds to the "cart"
> function in bench-streams.sml.  The name "gen" comes from your
> "Streams.ofArray" implementation and the "Stream.sum",
> "Stream.flatMap", and "Stream.map" code has been inlined and is being
> driven by the source stream --- which I think is precisely what you
> want for a push-based stream library.
>
> Note that MLton has a non-trivial implementation of "structure
> LargeInt" (see http://mlton.org/MLtonIntInf).  So, while you can find
> the "IntInf_mul" that corresponds to the "x * y" in the inner
> "Stream.map" and the "IntInf_add" that corresponds to the "a + ss" in
> the "Stream.sum", there is also a lot of supporting code that
> determines whether the operation can be done at a "small"
> fixed-precision or if it needs to be done at a "big"
> arbitrary-precision.  You might find it easier to investigate the
> exact form of the optimized program by switching from LargeInt to Int
> (or to Word, if you don't really care about the arithmetic result,
> since Word operations will silently wrap and not raise Overflow).
>
> If you look at the "gen_0" SSA IL function, you will see a non-tail
> recursive call of "gen_0" (which gives rise to a nested
> interprocedural loop) and a number of nested intraprocedural loops.  I
> would think that the non-tail recursive call corresponds to the
> non-tail call of "innerf(iterf)" in the "Stream.flatMap"
> implementation.  But, there isn't an unbounded stack growth:
>
> [mtf@fenrir sml-streams]$ git diff
> diff --git a/benchmarks/bench-streams.sml b/benchmarks/bench-streams.sml
> index f82cc9c..deb4d02 100644
> --- a/benchmarks/bench-streams.sml
> +++ b/benchmarks/bench-streams.sml
> @@ -67,8 +67,8 @@ fun main () =
>
>         (* Backing Arrays *)
>         val backingArr = Array.tabulate (3000000, fn i => i);
> -       val backingArrCart1 = Array.tabulate (100, fn i => Int.toLarge i);
> -       val backingArrCart2 = Array.tabulate (10, fn i => Int.toLarge i);
> +       val backingArrCart1 = Array.tabulate (10000000, fn i =>
> Int.toLarge i);
> +       val backingArrCart2 = Array.tabulate (100, fn i => Int.toLarge i);
>
>         (* Stream wrapping *)
>         val v = Stream.ofArray backingArr;
> [mtf@fenrir sml-streams]$ make bench
> make dirs
> mkdir -p "/Users/mtf/tmp/sml-streams/build"
> Type checking streams.
> "mlton" -verbose 0 -stop tc /Users/mtf/tmp/sml-streams/streams.mlb
> make dirs
> mkdir -p "/Users/mtf/tmp/sml-streams/build"
> Building bench-streams.
> "mlton" -verbose 0 -output
> /Users/mtf/tmp/sml-streams/build/bench-streams
> /Users/mtf/tmp/sml-streams/benchmarks/bench-streams.mlb
> [mtf@fenrir sml-streams]$ ./build/bench-streams @MLton gc-summary --
> Running
> Streams filters_6 time: 0.011 sec/op
> Baseline filters_6 time: 0.011 sec/op
> Streams cart time: 10.895 sec/op
> Baseline cart time: 7.897 sec/op
>
> Validation
> Streams  filters_6 = 2999984
> Baseline filters_6 = 2999984
> Streams  cart     = 247499975250000000
> Baseline cart     = 247499975250000000
> GC type time ms number  bytes      bytes/sec
> ------------- ------- ------- --------------- ---------------
> copying     50      2     80,031,032  1,600,620,640
> mark-compact      0      0              0              -
> minor      0      0              0              -
> total time: 18,883 ms
> total GC time: 117 ms (0.6%)
> max pause time: 116 ms
> total bytes allocated: 412,022,616 bytes
> max bytes live: 80,016,176 bytes
> max heap size: 640,196,608 bytes
> max stack size: 848 bytes
> num cards marked: 0
> bytes scanned: 0 bytes
> bytes hash consed: 0 bytes
>
> >From the "max stack size: 848 bytes", you can see that that non-tail
> recursive call is not repeated.  What I think you are seeing with the
> non-tail recursive call is that the same "gen_0" function is being
> used to drive the "Stream.map" on the v2 and the "Stream.flatMap" on
> the v1, where a defunctionalized closure ("datatype lambdas_1 = ... "
> in bench-streams.ssa) is being used to distinguish the two uses.
>
> Looking carefully at that defunctionalized closure:
>   lambdas_1 = Env_5 of (intInf array, (word64 * word64), lambdas_1) |
> Env_4 of (intInf, (word64 * word64), lambdas_1) | Env_3 of ((word64 *
> word64))
> we recognize Env_5 as the argument to Stream.flatMap (the "intInf
> array" is the "v2'" free variable) and Env_4 as the argument to
> Stream.map (the "intInf" is the "x" free variable).  More importantly,
> there is only one Env_5 construction in the program, which takes the
> one Env_3 construction as an argument.  Furthermore, all of the Env_4
> constructions are constructed with a lambdas_1 taken from a
> deconstructed Env_5.  So, the lambdas_1 component of all Env_5 and
> Env_4 values in the program are an Env_3.  Furthermore, the non-tail
> recursive call of "gen_0" is reachable only by deconstructing a Env_5,
> which convinces us that there is only ever two "gen_0" frames on the
> call stack.  If we were to unroll "gen_0" once and exploit this fact
> about the lambdas_1 component of Env_4 and Env_5 constructors, then we
> would achieve entirely intraprocedural loops with no non-tail
> recursive calls.
>
> While we wait for the compiler to learn that optimization, we can
> verify that things would be better if we didn't conflate the driving
> of the "Stream.map" on the v2 and the "Stream.flatMap" on the v1.  To
> do so, make a code-clone of "Stream.ofArray" and use one for v1 and
> the other for v2:
>
> [mtf@fenrir sml-streams]$ git diff
> diff --git a/benchmarks/bench-streams.sml b/benchmarks/bench-streams.sml
> index f82cc9c..9b9b83b 100644
> --- a/benchmarks/bench-streams.sml
> +++ b/benchmarks/bench-streams.sml
> @@ -67,13 +67,13 @@ fun main () =
>
>         (* Backing Arrays *)
>         val backingArr = Array.tabulate (3000000, fn i => i);
> -       val backingArrCart1 = Array.tabulate (100, fn i => Int.toLarge i);
> -       val backingArrCart2 = Array.tabulate (10, fn i => Int.toLarge i);
> +       val backingArrCart1 = Array.tabulate (10000000, fn i =>
> Int.toLarge i);
> +       val backingArrCart2 = Array.tabulate (100, fn i => Int.toLarge i);
>
>         (* Stream wrapping *)
>         val v = Stream.ofArray backingArr;
>         val v1 = Stream.ofArray backingArrCart1;
> -       val v2 = Stream.ofArray backingArrCart2;
> +       val v2 = Stream.ofArray2 backingArrCart2;
>
>         (* Stream functions *)
>         fun length values = Stream.length values;
> diff --git a/streams.sig b/streams.sig
> index c4c0426..2720c0b 100644
> --- a/streams.sig
> +++ b/streams.sig
> @@ -5,6 +5,7 @@
>  signature STREAM = sig
>      type 't stream
>      val ofArray : 't array -> 't stream
> +    val ofArray2 : 't array -> 't stream
>      val map    : ('t -> 'r) -> 't stream -> 'r stream
>      val filter : ('t -> bool) -> 't stream -> 't stream
>      val takeWhile : ('t -> bool) -> 't stream -> 't stream
> diff --git a/streams.sml b/streams.sml
> index 5b2e5ba..3e9b9df 100644
> --- a/streams.sml
> +++ b/streams.sml
> @@ -100,4 +100,21 @@ structure Stream : STREAM = struct
>        in
>           Stream gen
>        end
> +
> +  fun ofArray2 arr =
> +      let val gen =
> +          fn iterf =>
> +             let
> +                 val counter = ref 0
> +                 val cont = ref true
> +                 val size = Array.length arr
> +             in
> +                 while !counter < size andalso !cont do (
> +                     cont := iterf (Array.sub(arr, !counter));
> +                     counter := !counter + 1
> +                 )
> +             end
> +      in
> +         Stream gen
> +      end
>  end
> [mtf@fenrir sml-streams]$ make bench
> make dirs
> mkdir -p "/Users/mtf/tmp/sml-streams/build"
> Type checking streams.
> "mlton" -verbose 0 -stop tc /Users/mtf/tmp/sml-streams/streams.mlb
> make dirs
> mkdir -p "/Users/mtf/tmp/sml-streams/build"
> Building bench-streams.
> "mlton" -verbose 0 -output
> /Users/mtf/tmp/sml-streams/build/bench-streams
> /Users/mtf/tmp/sml-streams/benchmarks/bench-streams.mlb
> [mtf@fenrir sml-streams]$ ./build/bench-streams
> Running
> Streams filters_6 time: 0.011 sec/op
> Baseline filters_6 time: 0.011 sec/op
> Streams cart time: 7.269 sec/op
> Baseline cart time: 7.700 sec/op
>
> Validation
> Streams  filters_6 = 2999984
> Baseline filters_6 = 2999984
> Streams  cart     = 247499975250000000
> Baseline cart     = 247499975250000000
>
> Looking at bench-streams.ssa for this version of the program, you'll
> see that there is no "gen_0" function -- the corresponding code is no
> non-recursive and has been inlined into the "main_0" function.  The
> nested loops should be there, but somewhat lost amid all of the
> top-level initialization and exception handling code.  In any case,
> you get an improved throughput.
>
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
>
>
> ------------------------------------------------------------------------------
> Comprehensive Server Monitoring with Site24x7.
> Monitor 10 servers for $9/Month.
> Get alerted through email, SMS, voice calls or mobile push notifications.
> Take corrective actions from your mobile device.
>
> http://pubads.g.doubleclick.net/gampad/clk?id=154624111&iu=/4140/ostg.clktrk
> _______________________________________________
> MLton-user mailing list
> [email protected]; [email protected]
> https://lists.sourceforge.net/lists/listinfo/mlton-user
>

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

------------------------------------------------------------------------------
Comprehensive Server Monitoring with Site24x7.
Monitor 10 servers for $9/Month.
Get alerted through email, SMS, voice calls or mobile push notifications.
Take corrective actions from your mobile device.
http://pubads.g.doubleclick.net/gampad/clk?id=154624111&iu=/4140/ostg.clktrk

_______________________________________________
MLton-user mailing list
[email protected]; [email protected]
https://lists.sourceforge.net/lists/listinfo/mlton-user
Screenshot from 2014-11-15 01:25:30.png (image/png, 66.1 KB) - not displayed
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.