
profile - generates a runtime protocol

Calling sequence:
profile(expr)

Parameter:

expr   --  expression

Summary:

In order to be able to analyse the runtime behavior of a program in detail it
is necessary to protocol the runtime of the procedures that a program calls.
The number of procedure calls gives further information about the overall
behavior of a program. By using this information time-critical or runtime-
consuming program parts can be found. Thus, the user can easily recognise
those program parts that have to be optimized in order to reduce the runtime.
Checking the efficiency of the remember option of a procedure is a further
aspect of the runtime analyse of a program.

The profiles of the following programs that each calculate the n-th Fibonacci
number show the structure of a runtime protocol.

>>  fib1:=proc(n)
   begin
      if n<2 then n else fib1(n-1)+fib1(n-2) end_if:

    end_proc:
    profile(fib1(10)):

      Total time: 200 ms
      ------------------
      fib1: 100 % 200 ms total 177 call(s) 0 lookup(s) 1.1 ms/call

      <fib1> calls
        fib1 : 176 time(s)

Calculation of the 10th Fibonacci number takes 200ms.  Because, in this
example, the program only consists of the procedure fib1, the overall runtime
is equal to the runtime of the procedure fib1. Therefore, the runtime of fib1
takes 100% of the overall runtime. The procedure fib1 is called 177 times.
The mean runtime per call of fib1 is 1.1ms. The second part of the protocol
shows the procedures directly called from <proc> for every called procedure
<proc>. Additionally the number of calls is noted. The output above shows
that the procedure fib1 calls itself recursively.

>>  fib2:=proc(n)
       option remember;
    begin
       if n<2 then n else fib2(n-1)+fib2(n-2) end_if:
    end_proc:
    profile(fib2(10)):

      Total time: 20 ms
      -----------------

      fib2: 100 % 20 ms total 19 call(s) 8 lookup(s) 1.0 ms/call

      <fib2> calls
        fib2 : 18 time(s)
In contrast to the procedure fib1 fib2 uses the option remember.  The
calculation of the 10th Fibonacci number now only requires 19 calls of fib2,
whereby 8 calls of the result are calculated by being looked-up in the remember
table and not by executing the procedure body.

To illustrate this here is a more complicated example (the first run of this
example will produce a different statistic because the function have to be
loaded from file).

>>  profile(gcd(6*x^3+9*x^2*y^2, 2*x+2*x*y+3*y^2+3*y^3)):

      Total time: 30 ms
      -----------------
      gcdlib::expr2polys   : 33.3 % 10 ms total 1 call(s) 0 lookup(s) 10.0 ms/ca\
      ll
      gcdlib::special_cases: 33.3 % 10 ms total 1 call(s) 0 lookup(s) 10.0 ms/ca\
      ll
      gcd                : 33.3 % 10 ms total 1 call(s) 0 lookup(s) 10.0 ms/ca\
      ll
      gcdlib::coeff_type   :  0.0 % 0 ms total 1 call(s) 0 lookup(s) 0.0 ms/call\

      gcdlib::norm_intp    :  0.0 % 0 ms total 1 call(s) 0 lookup(s) 0.0 ms/call\

      <gcd> calls
        gcdlib::coeff_type    : 1 time(s)
        gcdlib::special_cases : 1 time(s)
        gcdlib::expr2polys    : 1 time(s)

      <gcdlib::special_cases> calls
        gcdlib::norm_intp : 1 time(s)

Only user-defined procedures are protocolled, not system functions.  If a
system function is to be protocolled then by simply re-defining system functions
as procedures a runtime protocol that also includes system functions can be
generated. The original program remains unchanged.

This processing can be illustrated using the heuristic algorithm for calculating
the greatest common divisor of two primitive polynomials with integer coefficients
(again the first run produces a different statistic).

>>  a := poly(
      9*x^5 + 2*x^4*y*z - 189*x^3*y^3*z + 117*x^3*y*z^2 + 3*x^3 -
      42*x^2*y^4*z^2 + 26*x^2*y^2*z^3 + 18*x^2 - 63*x*y^3*z +
      39*x*y*z^2 + 4*x*y*z + 6,
      [x, y, z]
    ):

    b := poly(
      6*x^6 - 126*x^4*y^3*z + 78*x^4*y*z^2 + x^4*y + x^4*z + 13*x^3 -
      21*x^2*y^4*z - 21*x^2*y^3*z^2 + 13*x^2*y^2*z^2 + 13*x^2*y*z^3 -
      21*x*y^3*z + 13*x*y*z^2 + 2*x*y + 2*x*z + 2,
      [x,y, z]

    ):

    profile(gcdlib::heu_gcd(a, b)):
      Total time: 100 ms

      ------------------
      gcdlib::heu_gcd: 99.9 % 100 ms total 3 call(s) 0 lookup(s)
                           33.3 ms/call

      <gcdlib::heu_gcd> calls
        gcdlib::heu_gcd : 2 time(s)

To examine the runtime more exactly the system functions igcd, genpoly
and evalp should also be protocolled.

>>  igcd_sys := igcd:
    evalp_sys := evalp:
    genpoly_sys := genpoly:
    igcd := proc() begin igcd_sys(args()) end_proc:
    evalp := proc() begin evalp_sys (args()) end_proc:
    genpoly := proc() begin genpoly_sys(args()) end_proc:

    profile(gcdlib::heu_gcd(a, b));

    igcd := igcd_sys:
    evalp := evalp_sys:
    genpoly := genpoly_sys:

      Total time: 70 ms
      -----------------

      gcdlib::heu_gcd: 71.4 % 50 ms total 3 call(s) 0 lookup(s)
                          16.6 ms/call
      evalp         : 28.5 % 20 ms total 6 call(s) 0 lookup(s)
                           3.3 ms/call
      igcd          :  0.0 %  0 ms total 1 call(s) 0 lookup(s)
                           0.0 ms/call
      genpoly       :  0.0 %  0 ms total 3 call(s) 0 lookup(s)
                           0.0 ms/call

      <igcd> calls
        evalp : 2 time(s)

      <gcdlib::heu_gcd> calls
        evalp          : 4 time(s)
        genpoly        : 3 time(s)
        igcd           : 1 time(s)
        gcdlib::heu_gcd : 2 time(s)

See also:  debug , trace

