If your code calls a procedure with the same arguments more than once, avoid unnecessary reevaluations, and thus, improve performance. Instead of multiple evaluations of a procedure call with the same arguments, MuPAD^{®} can store the results of the first procedure call in a special table. This table is called the remember table. The system stores the arguments of a procedure call as indices of the remember table entries, and the corresponding results as values of these entries. When you call a procedure using the same arguments as in previous calls, MuPAD accesses the remember table of that procedure. If the remember table contains the entry with the required arguments, MuPAD returns the value of that entry. Otherwise, MuPAD evaluates the procedure call, and writes the arguments and corresponding results to the remember table of the procedure.
Using the remember mechanism in MuPAD can significantly
accelerate your computations, especially when you use recursive procedure
calls. For example, create the procedure that computes the Lucas numbers.
The Lucas numbers are a sequence of integers. The recursion formula
that defines the n
th Lucas number is similar to
the definition of the Fibonacci numbers:
The following recursive procedure returns any Lucas number:
lucas:= proc(n:Type::PosInt) begin if n = 1 then 1 elif n = 2 then 3 else lucas(n - 1) + lucas(n - 2) end_if end_proc:
However, if the value n
is large, computing
the n
th Lucas number can be very slow. The number
of required procedure calls is exponential. Often, the procedure calls
itself with the same arguments, and it reevaluates the result in every
call:
time(lucas(35))
Using the remember mechanism eliminates these reevaluations.
To enable the remember mechanism for a particular procedure, use the prog::remember
function.
This function returns a modified copy of a procedure that stores results
of previous calls in the remember table:
lucas := prog::remember(lucas):
When you call this procedure, MuPAD accesses the remember table. If the system finds the required entry in the remember table, it returns remembered results immediately. Now, MuPAD computes the 35th and even the 100th Lucas number almost instantly:
time(lucas(35)), time(lucas(100))
Alternatively, you can enable the remember mechanism for a particular
procedure by using the option remember
for that
procedure. For example, use the option remember
to
enable the remember mechanism for the procedure lucas
:
lucas:= proc(n:Type::PosInt) option remember; begin if n = 1 then 1 elif n = 2 then 3 else lucas(n - 1) + lucas(n - 2) end_if end_proc:
For further computations, delete the procedure lucas
:
delete lucas:
By default, the remember mechanism does not consider context
information of a procedure call. Thus, the remember mechanism disregards
any changes in assumptions set on the arguments of a procedure call
and the number of digits used for floating-point arithmetic. By default,
remember tables contain only arguments and results of procedure calls.
They do not store context information. For example, create the function f
that
computes the reciprocal of a number. Use prog::remember
to enable the remember
mechanism for this function:
f := (x)-> 1.0/x: f := prog::remember(f):
The default number of significant digits for floating-point
numbers is 10. Use the function f
to compute the
reciprocal of 3. The system displays the result with the 10-digits
accuracy:
f(3)
Now increase the number of digits to 50. Then call the function f
with
the argument 3 again. By default, MuPAD does not realize that
you increased the required accuracy. The system accesses the remember
table, finds the entry that corresponds to the argument 3, and returns
the result previously computed for that argument. Since MuPAD must
display the output with 50 digits, the last digits in the displayed
result are incorrect:
DIGITS := 50: f(3)
For further computations, restore the default value of DIGITS
and
delete f
:
delete DIGITS, f
Although by default the remember mechanism in MuPAD disregards
all context information, you can extend the prog::remember
function call and take
into account the properties of arguments and current accuracy of floating-point
arithmetic. For example, create the function f
that
computes the reciprocal of a number. Use prog::remember
to enable the remember
mechanism for this function. In the prog::remember
function call, specify
the dependency function. The dependency function
is the function that computes the current properties of the input
arguments and the values of DIGITS
and ORDER
. Then prog::remember
compares
this context information with the context information used to compute
the remembered values. If the context information is the same, prog::remember
returns
the remembered result. Otherwise MuPAD evaluates the current
procedure call, and adds the new result to the remember table.
Note:
The option |
In this example, the dependency function is a list that checks
both the properties of input arguments and the value of DIGITS
:
f := (x)-> 1.0/x: f := prog::remember(f, () -> [property::depends(args()), DIGITS]):
The default number of significant digits for floating-point
numbers is 10. Use the function f
to compute the
reciprocal of 3. The system displays the result with the 10-digits
accuracy:
f(3)
If you set the number of digits to 50, and then call the function f
with
the same argument 3, prog::remember
realizes
that the number of digits has changed. Instead of returning the previous
result stored in the remember table, the system reevaluates the result
and updates the remember table:
DIGITS := 50: f(3)
For further computations, restore the default value of DIGITS
and
delete f
:
delete DIGITS, f
In some cases, the remember mechanism can lead to incorrect results. For example, if a nested procedure uses the remember mechanism, and you redefine the inner procedure, MuPAD does not recognize the changes and does not reevaluate the procedure call.
Create the following procedure f
as a wrapper
for the MuPAD heaviside
function.
Use prog::remember
to
enable the remember mechanism for the procedure f
:
f := proc(x) begin heaviside(x) end: f := prog::remember(f):
Now compute the Heaviside function for the values -10, 0, and
10. MuPAD uses the value heaviside(0)=1/2
:
f(-10), f(0), f(10)
You can define a different value for heaviside(0)
.
First, use the unprotect
function
to be able to overwrite the value of heaviside
. Then, assign the new value
to heaviside(0)
:
unprotect(heaviside): heaviside(0):= 0:
Despite the new value heaviside(0) = 0
, the
wrapper procedure f
returns the old value 1/2:
f(0)
The result of the procedure call f(0)
does
not change because the system does not reevaluate this result. It
finds the result in the remember table of the procedure f
and
returns that result. To display the content of the remember table,
call the wrapper procedure f
with the Remember
option
as a first argument and the Print
option as a second
argument. The value 10^{6} in
the second column is the value of MAXEFFORT
used during
computations.
f(Remember, Print)
To force reevaluation of the procedure calls of f
,
clear the remember table of that procedure. To clear the remember
table, call f
with the Remember
option
as a first argument and the Clear
option as a second
argument:
f(Remember, Clear):
Now f
returns the correct result:
f(0)
If you use the option remember
, you also
can clear the remember table and force reevaluation. For example,
rewrite the procedure f
as follows:
f := proc(x) option remember; begin heaviside(x) end: f(0)
Now restore the heaviside
function
to its default definition:
heaviside(0):= 1/2:
To clear a remember table created by the option remember
,
use the forget
function:
forget(f): f(0)
Use the protect
function
with the ProtectLevelError
option to prevent further
changes to heaviside
.
Also, delete the procedure f
:
protect(heaviside, ProtectLevelError): delete f
The remember mechanism is a powerful tool for improving performance of MuPAD procedures. Nevertheless, you can encounter some problems when using this mechanism:
Remember tables are efficient only if the access time of the remember table is significantly less than the time needed to evaluate the result. If a remember table is very large, evaluation can be computationally cheaper than accessing the result stored in the remember table.
Storing large remember tables requires a large amount
of memory. Especially, remember tables created with the option remember
can
grow very large, and significantly reduce available memory. The number
of entries in remember tables created by prog::remember
is limited. When the number
of entries in a remember table created by prog::remember
reaches the maximum number,
the system removes a group of older entries.
Using prog::remember
or
the option remember
for nonrecurring procedure
calls can significantly decrease code performance. Avoid using the
remember mechanism for nonrecurring procedure calls, especially if
the arguments are numerical.
If you change the properties of input arguments or
modify the variables DIGITS
or ORDER
, the remember
mechanism ignores these changes by default. See Remembering
Results Without Context.
In some cases you must clear the remember table of a procedure to enforce reevaluation and avoid incorrect results. For example, clearing the remember table can be necessary when a procedure changes global variables or if global variables affect the results of a procedure. See Clearing Remember Tables.
Many predefined MuPAD functions have special
values stored in their remember tables. Therefore, clearing the remember
tables of predefined MuPAD functions is not recommended. Note
that the forget
function
does not error when you call it for a predefined MuPAD function.