Analyzing and Tuning Statements
Analyzing and tuning statements
As illustrated in the previous section, carefully reviewing the number of elements in active subsets and the index domain conditions may lead to significant reductions in execution time. Additional reductions can be obtained by analyzing and rewriting specific time-consuming statements and constraints. In this section we will discuss a procedure which you can follow to identify and resolve potential inefficiencies in your model.
Suggested approach
You can use the AIMMS profiler to identify computational bottlenecks in your model. If you have found a particular bottleneck, you may want to use the checklist below to quickly find relevant information for the problem at hand. For each question that you answer with a yes you may want to follow the suggested option.
Is the bottleneck a repeated expression where the combined execution of all instances takes up a lot of time? If so, you can either
manually replace the expression by a new parameter containing the repeated expression as a definition. Do not forget to check the
NoSave
property if you do not want that newly defined parameter to be stored in cases.or let AIMMS do it for you, by setting the option
subst low dim expr class
to an appropriate value for your application. See also the help associated with that option.
For a worked example, see also Identifying Lower-Dimensional Subexpressions
Is the bottleneck due to debugging/obsolete code? If so, delete it, move it to the
Comment
attribute, or enclose the time-consuming debugging code in something like anIF ( DebugMode ) THEN
andENDIF
pair.Are you using dense operators such
/
,=
,^
, or dense functions such asLog
,Exp
,Cos
in which a zero argument has a non-zero result? An overview of the efficiency of such functions and operators can be found in Overview of Operator Efficiency. Could you add index domain conditions to make the execution of the time-consuming expressions more sparse, without changing the final result?Is the bottleneck part of a
FOR
statement? If so, is thatFOR
statement really necessary? For a detailed discussion about the need for and alternatives toFOR
statements, see Consider the Use of FOR Statements.Is the bottleneck the condition of the
FOR
statement that takes up most of the time? This is shown in the profiler by a large net time for theFOR
statement. Ordered Sets and the Condition of a FOR Statement discusses why the conditions ofFOR
statements may absorb a lot of computational time and discusses alternatives.Does the body of a
FOR
,WHILE
, orREPEAT
statement contain aSOLVE
statement, and is AIMMS spending a lot of time regenerating constraints (as shown in the profiling times of the constraints)? If so, consider modifying the generated mathematical programs directly using the GMP library as discussed in Implementing Advanced Algorithms for Mathematical Programs.Does your model contain a defined parameter over an index, say
t
, and do you use this parameter inside aFOR
loop that runs over that same indext
? Inefficient use of this construct is indicated by the AIMMS profiler through a high hitcount for that defined parameter. See Combining Definitions and FOR Loops for an example and an alternative formulation.Is the bottleneck an expression with several running indices? Contains this expression non-trivial sub-expressions with fewer running indices? If the answer is yes, consult Identifying Lower-Dimensional Subexpressions for a detailed analysis of two examples.
Does the expression involve a parameter or a variable that is bound with a non-zero default? Parameters with Non-Zero Defaults discusses the possible adverse timing effects of using non-zero defaults in expressions, and how to overcome these.
Would you expect a time-consuming assignment to take less time given the sparsity of the identifiers involved? This may be one of those rare occasions in which the specific order of running indices has an effect on the execution speed. Although tackling this type of bottleneck may be very challenging, Index Ordering hopefully offers sufficient clues through an illustrative example.
Are you using ordered sets? Reordering the elements in a set can slow execution significantly as detailed in Set Element Ordering.
If you want hands on experience with such examples, please check out the last chapter of the AIMMS Academy course “Execution Efficiency”. In this course, concrete AIMMS projects are offered to experiment with.
Consider the Use of FOR
Statements
Why avoid the FOR
statement?
The AIMMS execution system is designed for efficient bulk execution of
assignment statements, plus set and parameter definitions and
constraints. A consequence of this design choice is that computation
time is spent, just before the execution of such an executable object,
analyzing and initializing that object. This is usually worthwhile
except when only one element is computed at a time. Consider the
following two fragments of AIMMS code that have the same final result.
The first fragment uses a FOR
statement:
for ( (i,j) | B(i,j) ) do ! Only when B(i,j) exists we want to
A(i,j) := B(i,j); ! overwrite A(i,j) with it.
endfor ;
The second fragment avoids the FOR
statement:
A((i,j) | B(i,j)) := B(i,j); ! Overwrite A(i,j) only when B(i,j) exists
In the first fragment, the initialization and analysis is performed for
every iteration of the FOR
loop. In the second fragment the
initialization and analysis is performed only once. Using the $
sparsity modifier on the assignment operator :=
(see also
Modifying the Sparsity), the statement can be formulated even more
compactly and efficiently as:
A(i,j) :=$ B(i,j); ! Merge B(i,j) in A(i,j)
In the above example, the FOR
statement is used only to restrict the
domain of execution of a single assignment. While using the FOR
statement in this manner may seem normal to programmers, the execution
engine of AIMMS can deal with conditions on assignment statements much
more efficiently. As such, the use of the FOR
statement is
superfluous and time consuming.
When not to remove the FOR
statement
Now that the FOR
statement has been made to look inefficient, you
are probably wondering why has it been introduced in the AIMMS language
in the first place? Well, simply because sometimes it is needed. And it
is only inefficient if used unnecessarilly. So when is the FOR
statement applicable? Two typical examples are:
generating a text report file, and
in algorithmic code inside the core model.
We will discuss these examples in the next two paragraphs.
Generating text reports
The AIMMS DISPLAY
statement is a high level command that outputs an
identifier in tabular, list, or composite list format with a limited
amount of control. In addition, the output of the DISPLAY
statement
can always be read back by AIMMS, and, to enable that requirement, the
name of the identifier is always included in the output. Thus, the AIMMS
DISPLAY
statement usually fails to meet the specific formatting
requirements of your application domain, and you end up needing control
over the position of the output on an element-by-element basis. This
requires the use of FOR
statements. However, depending on the
purpose of your text report file, there might be very good alternatives
available:
When this reporting is for printing purposes only, you may want to consider the AIMMS print pages as explained in Print configuration. These print pages look far better than text reports.
Warning
Print pages functions are about to be deprecated with the WinUI, please refer to AIMMS Product Lifecycle. You may use the WebUI instead.
When the report file is for communication with other programs, you may want to consider whether communication using relational databases (see Communicating With Databases), or through XML (see Reading and Writing XML Data) form better alternatives. For communication with EXCEL or OpenOffice Calc, a library of dedicated functions is built in AIMMS (see Reading and Writing Spreadsheet Data).
Algorithmic code inside the core model
A FOR
statement is needed whenever your model contains two
statements where:
the computation of the last statement depends on the computation of the first statement, and
the computation of the first statement depends on the results of the last statement obtained during a previous iteration.
Iterating unneccesarily
FOR
statements may be especially inefficient, if the condition of a
FOR
statement allows elements for which none of the statements
inside the FOR
loop modify the data in your model or generate
output. This is illustrated in the following example.
Transposing a distance matrix
Consider a distance matrix, D(i,j)
, with only a few entries per row
in its lower left half containing the distances to near neighbors. You
also want it to contain the reverse distances. One, inefficient, but
valid, way to formulate that in AIMMS is as follows:
for ( (i,j) | i > j ) do ! The condition 'i > j' ensures we only
D(i,j) := D(j,i) ; ! write to the upper right of D.
endfor ;
Why inefficient?
There are two reasons why the above is inefficient:
Although there is a condition on the
FOR
loop, this condition permits many combinations of(i,j)
that do not invoke execution asD(i,j)
was sparse to begin with. A tempting improvement would be to addD(j,i)
to the condition on theFOR
loop. However, this will lead to other problems, however, as will be explained in the next section.As explained in Reordered Views, AIMMS maintains reordered views. For each non-zero value computed and assigned to the identifier
D(i,j)
, AIMMS will need to adapt the reordered view forD(j,i)
, and re-initialize searching in that reordered view.
Suggested modification
In the example at hand we can move the condition on the FOR
loop to
the assignment itself and simply remove the FOR
statement altogether
(but not its contents). The example then reads:
D((i,j) | i > j) := D(j,i) ; ! The condition 'i > j' ensures we only
! write to the upper right of D.
Using application domain knowledge
We can improve the assignment further by noting that we are actually
merging the transposed lower half in the identifier itself, and that
there is no conflict in the elements. This can be achieved by a $
sparsity modifier on the assignment operator. The $
sparsity
modifier and the opportunity it offers are introduced in
Modifying the Sparsity. The example can then be written as:
D(i,j) :=$ D(j,i); ! Merge the transpose of the lower half in the identifier itself.
Ordered Sets and the Condition of a FOR
Statement
Modifying the FOR
condition
The condition placed on a FOR
statement is like any other expression
evaluated one element at a time. However, during that evaluation, the
identifiers referenced in the condition may have been modified by the
statements inside the FOR
loop. In general, this is not a problem,
except when the range of the running index of the FOR
statement is
an ordered set. In that situation, the evaluation of the condition
itself becomes time consuming as the tuples satisfying the condition
have to be repeatedly computed and sorted, as illustrated below.
Continued example
Let us again consider the example of the previous section with the
parameter D
now added to the FOR
loop condition, and the set
S
ordered lexicographically. As an efficient formulation has already
been presented in the previous section, it looks somewhat artificial,
but similar structures may appear in real-life models.
Set S {
Index : i,j;
OrderBy : i ! lexicographic ordering.;
Body : {
for ( (i,j) | ( i > j ) AND D(j,i) ) do ! Only execute the statements in the
D(i,j) := D(j,i) ; ! loop when this is essential.
endfor
}
}
What does AIMMS do in this example?
First note that the FOR
statement respects the ordering of the set
S
. Because of this ordering, AIMMS will first evaluate the entire
collection of tuples satisfying the condition ( i > j ) AND D(j,i)
,
and subsequently order this collection according to the ordering of the
set S
. Next, the body of the FOR
statement is executed for every
tuple in the ordered tuple collection. However, when an identifier, such
as D
in this example, is modified inside the body of the FOR
loop AIMMS will need to recompute the ordered tuple collection, and
continue where it left off. This not only sounds time consuming, it is.
FOR
as a bottleneck
If the following three conditions are met, the condition of a FOR
statement becomes time consuming:
the indices of a
FOR
statement have a specified element order,the condition of the
FOR
statement is changed by the statements inside the loop, andthe product of the cardinality of the sets associated with the running indices of the
FOR
statement is very large.
if these three conditions are met, AIMMS will issue a warning when the number of re-evaluations reaches a certain threshold.
Improving efficiency
There are several ways to improve the efficiency of inefficient FOR
statements. To understand this, it is necessary to explain a little more
about the execution strategies available to AIMMS when evaluating
FOR
statements, as each strategy has its own merits and drawbacks.
Therefore, consider the FOR
statement:
for ( (i,j,k) | Expression(i,j,k) ) do
! statements ...
endfor;
where i
, j
and k
are indices of some sets, each with a
specified ordering, and Expression(i,j,k)
is some expression over
the indices i
, j
and k
.
The sparse strategy
The first strategy, called the sparse strategy, fully evaluates
Expression(i,j,k)
, and stores the result in temporary storage before
executing the FOR
statement. Subsequently, for each tuple
(i,j,k)
for which a non-zero value is stored, the statements within
the FOR
loop are executed. If an identifier is modified during the
execution of these statements, then the condition Expression(i,j,k)
has to be fully re-evaluated.
The dense strategy
The second strategy, called the dense strategy, evaluates
Expression(i,j,k)
for all possible combinations of indices
(i,j,k)
. As soon as a non-zero result is found the statements are
executed. Re-evaluation is avoided, but at the price of considering
every (i,j,k)
combination.
The unordered strategy
The third strategy, called the unordered strategy, uses the normal
sparse execution engine of AIMMS but ignores the specified order of the
indices. This may, however, give different results, especially when the
FOR
loop contains one or more DISPLAY
/PUT
statements or uses
lag and lead operators in conjunction with one or more of the ordered
indices.
Selecting a strategy
By prefixing the FOR
statement with one of the keywords SPARSE
,
ORDERED
, or UNORDERED
(as explained in
The FOR Statement), you can force AIMMS to adopt a particular
strategy. If you do not explicitly specify a strategy, AIMMS uses the
sparse strategy by default, and only issues a warning if an identifier
referenced inside the FOR
loop is modified and the second evaluation
of Expression(i,j,k)
gives a non-empty result.
Improving efficiency
Given the above, you have the following options for improving the
efficiency of the FOR
statement.
Rewrite the
FOR
statement such that the condition does not change during each iteration.Prefix the
FOR
statement with the keywordUNORDERED
such that the unordered strategy will be set. You can safely choose this strategy if the element order is not relevant for theFOR
statement. In all other cases, the semantics of theFOR
statement will be changed.Prefix the
FOR
statement with the keywordORDERED
such that the dense strategy is selected. You can safely choose this strategy if the condition on the running indices evaluates to true for a significant number of all possible combinations of the tuples(i,j,k)
.Prefix the
FOR
statement with the keywordSPARSE
to adopt the sparse strategy. However, all warnings will be suppressed relating to the condition on the running indices needing to be evaluated multiple times. You can choose this strategy if the condition needs to be re-evaluated in only a few iterations.
Combining Definitions and FOR
Loops
Dependency is symbolic
As explained in Dependency Structure of Definitions, the dependency structure between
set and parameter definitions is based only on symbol references. AIMMS’
evaluation scheme recomputes a defined parameter in its entirety even
if only a single element in its inputs has changed. This negatively
affects performance when such a defined parameter is used inside a
FOR
loop and its input is changed inside that same FOR
loop.
A simulation example
A typical example occurs when using definitions in simulations over
time. In simulations, computations are often performed period by period,
referring back to data from previous period(s). The relation used to
computate the stock of a particular product p
in period t
can
easily be expressed by the following definition and then used inside the
body of a procedure.
Parameter ProductStock {
IndexDomain : (p,t);
Definition : ProductStock(p,t-1) + Production(p,t) - Sales(p,t);
}
Procedure ComputeProduction {
Body : {
for ( t ) do
! Compute Production(p,t) partly based on the stock for period (t-1)
Production(p,t) := Max( ProductionCapacity(p),
MaxStock(p) - ProductStock(p,t-1) + Sales(p,t) );
endfor ;
}
}
During every iteration, the production in period t
is computed on
the basis of the stock in the previous period and the maximum production
capacity. However, because of the dependency of ProductStock
with
respect to Production
, AIMMS will re-evaluate the definition of
ProductStock
in its entirety for each period before executing the
assignment for the next period. Although the FOR
loop is not really
necessary here, it is used for illustrative purposes.
Improved formulation
In this example, execution times can be reduced by moving the definition
of ProductStock
to an explicit assignment in the FOR
loop.
Parameter ProductStock {
IndexDomain : (p,t);
! Definition attribute is empty.
}
Procedure ComputeProduction {
Body : {
for ( t ) do
! Compute Production(p,t) partly based on the stock for period (t-1)
Production(p,t) := Max( ProductionCapacity(p),
MaxStock(p) - ProductStock(p,t-1) + Sales(p,t) );
! Then compute stocks for current period t
ProductStock(p,t) := ProductStock(p,t-1) + Production(p,t) - Sales(p,t);
endfor ;
}
}
In this formulation, only one slice of the ProductStock
parameter is
computed per period. A drawback of this formulation is that it will have
to be restated at various places in your model if the inputs of the
definition are assigned at several places in your model.
Use of macros
As an alternative, you might consider the use of a Macro
(see also
MACRO Declaration and Attributes) to localize the defining expression of
ProductStock
at a single node in the model tree. The disadvantage of
macros is that they cannot be used in DISPLAY
statements, or saved
to cases.
When to avoid definitions
As illustrated above, it is best to avoid definitions, if, within a
FOR
loop, you only need a slice of that definition to modify the
inputs for another slice of that same definition. AIMMS is not designed
to recognize this situation and will repeatly evaluate the entire
definition. The AIMMS profiler will expose such definitions by their
high hitcount.
Identifying Lower-Dimensional Subexpressions
Lower-dimensional subexpressions
Repeatedly performing the same computation is obviously a waste of time. In this section, we will discuss a special, but not uncommon, instance of such behavior, namely lower-dimensional sub-expressions. A lengthy expression, that runs over several indices, can have distinct subexpressions that depend on fewer indices. Let us illustrate this with two examples, the first being an artificial example to explain the principle, and the second a larger example that has actually been encountered in practice and permits the discussion of related issues.
Artificial example
Consider the following artificial example:
F(i,k) := G(i,k) * Sum[j | A(i,j) = B(i,j), H(j)] ;
For every value of i
, the sub-expression
Sum[j | A(i,j) = B(i,j), H(j)]
results in the same value for each
k
. Currently, the AIMMS execution engine will repeatedly compute
this value. It is more efficient to rewrite the example as follows.
FP(i) := Sum[j | A(i,j) = B(i,j), H(j)] ;
F(i,k) := G(i,k) * FP(i) ;
The principle of introducing an identifier for a specific sub-expression often leads to dramatic performance improvements, as illustrated in the following real-life example.
A complicated assignment
Consider the following 4-dimensional assignment involving
region-terminal-terminal-region transports. Here, sr
and dr
(source region and destination region) are indices in a set of
Regions
with m
elements and st
and dt
(source terminal
and destination terminal) are indices in a set of Terminals
with
n
elements.
Transport( (sr,st,dt,dr) | TRDistance(sr,st) <= MaxTRDistance(st) AND
TRDistance(dr,dt) <= MaxTRDistance(dt) AND
sr <> dr AND MinTransDistance <= RRDistance(sr,dr) <= MaxTransDistance AND
st <> dt AND MinTransDistance <= TTDistance(st,dt) <= MaxTransDistance
) := Demand(sr,dr);
The domain condition states that region-terminal-terminal-region transport should only be assigned if the various distances between regions and/or terminals satisfy the given bounds.
Efficiency analysis
The <=
operator is dense and be evaluated for all possible values of
the indices. The subexpression
TRDistance(sr,st) <= MaxTRDistance(st)
, for example, will be
evaluated for every possible value of dr
and dt
, even though it
only depends on sr
and st
. In other words, we’re computing the
same thing over and over again.
Effect of the AND
operator
There are multiple AND
operators in this example. The AND
operator is sparse, and oten, sparse operators make execution quick.
However, they fail to do just that in this particular example. Bear with
us. Although the AND
operator is a sparse binary operator, its
effectiveness depends on how effectively the intersection is taken. What
are we taking the intersection of? If we consider a particular argument
of the AND
operators: TRDistance(sr,st) <= MaxTRDistance(st)
, as
the operator <=
is dense and this argument will be computed for all
tuples {(sr,st,dt,dr)}
even though the results will be mostly 0.0’s.
The domain of evaluation for this argument is thus the full Cartesian
product of four sets. The evaluation domain of the other arguments of
the AND
operators will be the same. So, in this example, we are
repeatedly taking the intersection of a Cartesian product with itself,
resulting in that same Cartesian product. Thus, the AND
operator
will be evaluated for all tuples in {(sr,st,dt,dr)}
even though this
operator is sparse.
Example reformulation
In the formulation below, we’ve named the following sub-expressions.
ConnectableRegionalTerminal( (sr,st) | TRDistance(sr,st) <= MaxTRDistance(st) ) := 1;
ConnectableRegions( (sr,dr) | sr <> dr AND
MinTransDistance <= RRDistance(sr,dr) <= MaxTransDistance ) := 1;
ConnectableTerminals( (st,dt) | st <> dt AND
MinTransDistance <= TTDistance(st,dt) <= MaxTransDistance ) := 1;
In each of these three assignments, each condition depends fully on the running indices and thus its evaluation is not unnecessarily repeated. By substituting the three newly introduced identifiers in the condition the original assignment becomes:
Transport( (sr,st,dt,dr) |
ConnectableRegionalTerminal(sr,st) AND
ConnectableRegionalTerminal(dr,dt) AND
ConnectableRegions(sr,dr) AND
ConnectableTerminals(st,dt) )
:= Demand(sr,dr);
The newly created identifiers are all sparse, and the sparse operator
AND
can effectively use this created sparsity in its arguments.
Effect of reformulation
A modified version of the above example was sent to us by a customer. While the original formulation took several minutes to execute for a given large dataset, the reformulation only took a few seconds.
Parameters with Non-Zero Defaults
Sparse execution expects 0.0’s
Sparse execution is based on the effect of the number 0.0 on addition
and multiplication. When other numbers are used as a default, all
possible elements of these parameters need to be considered rather than
only the stored ones. The advice is not to use such parameters in
intensive computations. In the example below, the summation operator
will need to consider every possible element of P
rather than only
its non-zeros.
Parameter P {
IndexDomain : (i,j);
Default : 1;
Body : {
CountP := Sum( (i,j), P(i,j) )
}
}
Appropriate use of default
Identifiers with a non-zero default, may be convenient, however, in the interface of your application as the GUI of AIMMS can display non-default elements only.
The NonDefault
function
For parameters with a non-zero default, you still may want to execute
only for its non-default values. For this purpose, the function
NonDefault
has been introduced. This function allows one to limit
execution to the data actually stored in such a parameter. Consider the
following example where the non defaults of P
are summed:
CountP := Sum( (i,j)| NonDefault(P(i,j)), P(i,j) );
In the above example the summation is limited to only those entries in
P(i,j)
that are stored. If you would rather use familiar algebraic
notation, instead of the dedicated function NonDefault
, you can
change the above example to:
CountP := Sum( (i,j) | P(i,j) <> 1, P(i,j) );
This statement also sums only the non-default values of P
. AIMMS
recognizes this special use of the <>
operator as actually using the
NonDefault
function; the summation operator will only consider the
tuples (i,j)
that are actually stored for P
.
Suffices of variables
Note that the suffices .Lower and .Upper of variables are like
parameters with a non-zero default. For example in a free variable the
default of the .lower
suffix is -inf
and the default of the
suffix .upper
is inf
.
Index Ordering
Index ordering
In rare cases, the particular order of indices in a statement may have an adverse effect on its performance. The efficiency aspects of index ordering, when they occur, are inarguably the most difficult to understand and recognize. Again, this inefficiency is best explained using an example.
An artificial example
Consider the following assignment statement:
FS(i,k) := Sum( j, A(i,j) * B(j,k) );
If A(i,j)
and B(j,k)
are binary parameters, where
for any given
i
, the parameterA(i,j)
maps to a singlej
, and,for any given
j
, the parameterB(j,k)
maps to a singlek
,
one would intuitively expect that the assignment could be executed rather efficiently. When actually executing the statement, it may therefore come as an unpleasant surprise that it takes a seemingly unexplainable amount of time.
An analysis
In the qualitative analysis above, implicitly the index order i
selects j
, and j
selects a few k
‘s, or, in AIMMS
terminology, a running index order [i,j,k]
. The actual running index
order of AIMMS is, however, first the indices [i,k]
from the
assignment operator, followed by the index [j]
from the summation
operator: [i,k,j]
. The effect of the actual index order is that, for
a given value of index i
, the relevant values of index k
cannot
be restricted using the parameter chain A(i,j)
-B(j,k)
without
the aid of an intermediate running index j
. Consequently, AIMMS has
to try every combination of (i,k)
.
Reformulation
Given the above analysis, the preferred index ordering [i,j,k]
can
be accomplished by introducing an intermediate identifier
FSH(i,j,k)
, and replacing the original assignment by the following
statements.
FSH(i,j,k) := A(i,j) * B(j,k);
FS(i,k) := Sum( j, FSH(i,j,k) );
With a real-life example, where the range of the indices i
, j
and k
contained over 10000 elements, the observed improvement was
more than a factor 50.
Not for +
A similar improvement could be obtained for the following example:
FSP(i,k) := Sum( j, A(i,j) + B(j,k) );
Here a value is computed for each (i,k)
of FSP
, because, for
every i
, there is a non-zero A(i,j)
, and for every k
, there
is a non-zero B(j,k)
.
Set Element Ordering
Data entry order
By default, all elements in a root set are numbered internally by AIMMS in a consecutive manner according to their data entry order, i.e. the order in which the elements have been added to the set. Such additions can be either explicit or implicit, and may take place, for example when the model text contains references to explicit elements in the root set, or by reading the set from files, databases, or cases.
Multidimensional storage
The storage of multidimensional data defined over a root set is always based on this internal and consecutive numbering of root set elements. More explicitly, all tuple-value pairs associated with a multidimensional identifier are stored according to a strict right-to-left ordering based on the respective root set numberings.
Indexed execution
By default, all indexed execution taking place in AIMMS, either through
implied loops induced by indexed assignments or through explicit FOR
loops, employs the same strict right-to-left ordering of root set
elements. Thus, there is a perfect match between the execution order and
the order in which identifiers referenced in such loops are stored
internally. As a consequence, it is very easy for AIMMS to synchronize
the tuple for which execution is currently due with an ordered route
through all the non-zero tuples in the identifiers involved in the
statement. This principle is the basis of the sparse execution engine
underlying AIMMS.
Execution over ordered sets
Inefficiency is introduced if the elements in a set over which a loop
takes place have been ordered differently from the data entry order,
either because of an ordering principle specified in the OrderBy
attribute of the set declaration or through an explicit Sort
operation. Consequently, there will no lomger be a direct match between
the execution order of the loop and the storage order of the non-zero
identifier values. Depending on the precise type of statement, this may
result in no, slight or serious increase in the execution time of the
statement, as AIMMS may have to perform randomly-placed lookups for
particular tuples. These random lookups are much more time consuming
than running over the data only once in an ordered fashion.
Effect on FOR
loops
In particular, you should avoid using FOR
statements in which the
running index is an index in a set with a nondefault ordering whenever
possible. If not, AIMMS is forced to execute such FOR
statements
using the imposed nondefault ordering and, as a result, all identifier
lookups within the FOR
loop are random. In such a situation, you
should carefully consider whether ordered execution is really essential.
If not, it is advisable to leave the original set unordered, and create
an ordered subset (containing all the elements of the original set)
for use when the nondefault element ordering is required.
Effect on assignments
In most situations, the efficiency of indexed assignments is not affected by the use of indices in sets with a nondefault ordering. AIMMS has only to rely on the nondefault ordering if an assignment contains special order-dependent constructs such as lag and lead operators. In all other cases, AIMMS can use the default data entry order.
Complete reordering
If a nondefault ordering of some sets in your model causes a serious
increase in execution times, you may want to apply the
CLEANDEPENDENTS
statement (see also Data Control) to
those roots sets that are the cause of the increase of execution times.
The CLEANDEPENDENTS
statement will fully renumber the elements in
the root set according to their current ordering, and rebuild all data
defined over it according to this new numbering.
Use sparingly
As all identifiers defined over the root set have to be completely
rebuilt, CLEANDEPENDENTS
is an inherently expensive operation. You
should, therefore, only use it when really necessary.
Using AIMMS’ Advanced Diagnostics
Using diagnostic warnings
In order to help you create correct and efficient applications, AIMMS is regularly extended with diagnostics that incorporate the recognition of new types of problematic situations. These diagnostics may help you detect model formulations that lead to sub-optimal performance and/or ambiguous results. These diagnostics can be controled through various options in the Warning category.
Apply diagnostics regularly
As the list of diagnostic options is regularly extended, and some of the
formulation problems depend on the model data and, thus, can only be
detected at runtime, you are advised to apply the diagnostics provided
by AIMMS on a regular basis during your application tests.
Warnings describes a way in which you can switch on
all the diagnostic options by just changing the value of the two options
strict_warning_default
and common_warning_default
.
Diagnostic options
Below we provide a list of performance-related diagnostics that may help you tune the performance of your model:
Warning_repeated_iterative_evaluation
If the arguments of an iterative operator do not depend on some of the indices, the iterative operator is repeatedly evaluated with the same result. Consider the assignment
a(i) := sum(j,b(j));
in which the sum does not depend on the indexi
and so the same value is computed for every value ofi
. See also Identifying Lower-Dimensional Subexpressions.Warning_unused_index
If an index is not used inside the argument(s) or index domain condition of an iterative iterator, this leads to inefficient execution. In the assignment
a(i) := sum((j,k),b(i,j));
, the indexk
is not used in the summation. Further, when an index in the index domain of a constraint is not used inside the definition of that constraint this is likely to lead to the generation of duplicate rows.Warning_duplicate_row
At the end of generating a mathematical program it is verified that there are no duplicate rows inside that mathematical program. This might be caused by two constraints having the same definition. Besides consuming more memory, duplicate rows cause the problem to become degenerate and may cause the problem to become more difficult to solve. This warning is not supported for mathematical programs of type MCP or MPCC because, for these types the row col mapping is also relevant and duplicate rows cannot be simply eliminated.
Warning_duplicate_column
At the end of generating a mathematical progrram it is verified that there are no duplicate columns inside that mathematical program. Besides consuming more memory, duplicate columns result in the generated mathematical program having non-unique solutions.
Warning_trivial_row
Generating and eliminating trivial rows such as \(0 <= 1\) takes time.
The help for the option category AIMMS
-
Progress, errors & warnings
- warnings
provides more information
on these options.