Package: opoly
OPolyPolyhedral compilation library for automatic code parallelization in OpenMP written in python.DependenciesPython 3.9+ is required in order to use OPoly and to install it.The python dependecies are included in the installation withpipand are listed here:numpy(>=1.19)sympy(>=1.7)pymzn(>=0.18.3)MinizincThe only non-python dependency which you have to take care of is theMinizincCLI tool that is used to model and solve the constraint programming problems of the polyhedral approach. The easiest way to install MiniZinc is to download the MiniZincIDE package, which contains both the MiniZinc binaries and several solvers. To install it, followthisguide. After installation, be sure that the executables are set in yourPATHenvironment variable. One way to make sure about this is to try to execute theminizinccommand from the console.InstallationThe recommended way to install OPoly is to install it withpipfrom thePyPIrepository:pip install opolyYou can also install OPoly from the source code available onGitHubalso withpip:pip install .or the old way:python setup.py installNote: consider using avirtualenvor acondaenvironment to install OPoly into.UsageAfter installing OPoly, you will have access to the command line toolopoly. This script reads a file containing code that needs to be parallelized. The code must be given in a properly formatted, pseudocode language that will be described later.The script will parse this file, detect the loop-carried dependencies and perform the polyhedral optimization using the method described by Lamport in his famous articleThe Parallel Execution of DO Loops.The script will then generate the parallelized version of the code in the same pseudocode language or in C syntax, adding OpenMP directives where needed.Suppose that the file containing the input code is namedexample1.psc, then the command required to generate the parallel code is:opoly example1.pscBy default,opolywill output in the standard output the generated code in C syntax (with OMP directives).
To output the code in pseudocode syntax, one can use the-for--formatoption to specify the format of the resulting code toPSEUDO:opoly example1.psc -f PSEUDOThe-oor--outputargument can be used to specify a custom file in which the resulting code should be written, instead of the standard output:opoly example1.psc -o omp-example1.cFor more information about theopolycommand line tool, read the help with:opoly -hDescriptionThe most common usage of theopolytool is to read a file containing the nested loop(s) that needs to be parallelized. Let's say that the file contains this code:FOR k FROM 1 TO q {
    FOR i FROM 1 TO n-2 {
        STM a[i] = (a[i-1] + a[i] + a[i+1]) / 3.0;
    }
}Let's not care too much now about the syntax of the pseudocode as it will be described later, but to get a grasp of what this code is representing, we are going to describe it in a few words.First, there are 2 nested loops: the outer loop iterates through variablekthat assumes integer values from1toq; the inner loop iterates through variableifrom1ton-2. There is a statement regarding vectorathat assigns the element in positionito the sum of the elements in positioni-1,iandi+1divided by3, at iterationiof the inner loop.We can see that this code is not parallelizable just yet, because there are loop-carried dependecies regarding the computation of certain values of vectora. For example, we cannot computea[i]anda[i-1]in parallel, since one value depends on the other.opolyis able to rewrite the loops in a way that the second one can be parallelized without changing the result of the computation. The pseudocode version of the rewritten code is:FOR new_k FROM 3 TO n + 2 * q - 2 STEP 1 {
    FOR CONC new_i FROM fmax(1, ceil((1.0 / 2.0) * (-n + new_k + 2))) TO fmin(q, floor((1.0 / 2.0) * (new_k - 1))) STEP 1 {
        VAR k = new_i;
        VAR i = -2 * new_i + new_k;
        STM a[i] = (a[i - 1] + a[i] + a[i + 1]) / 3.0;
    }
}Two new index variables are introduced:new_kandnew_i. These new indexes assume values in new ranges, which alltogether form the lattice of points inside the resulting polyhedron.The values of the original variables are recomputed inside the innermost loop with the statements:...
VAR k = new_i;
VAR i = -2 * new_i + new_k;
...The statement that perform the computation remains unchanged.There are a few functions in the loop range that compute the maximum (fmax), minimum (fmin), ceiling (ceil) and floor (floor) of their arguments.opolycan also rewrite the code in C syntax and add OMP directives to perform parallel loops. The C version of the rewritten code is:for(intnew_k=3;new_k<=n+2*q-2;new_k++){intnew_i_lb=fmax(1,ceil((1.0/2.0)*(-n+new_k+2)));intnew_i_ub=fmin(q,floor((1.0/2.0)*(new_k-1)));#pragma omp parallel forfor(intnew_i=new_i_lb;new_i<=new_i_ub;new_i++){intk=new_i;inti=-2*new_i+new_k;a[i]=(a[i-1]+a[i]+a[i+1])/3.0;}}Notice that the values of the lower and upper bounds of the inner loop are computed before the loop statement in this version. The result is a more readable code, not impacting performance.Pseudocode syntaxWe will now describe the syntax of the pseudocode.We must first define the difference between indexes, parameters and vectors:anindexis a for loop index variable (eg.iorkin the examples above).aparameteris every variable in the program for which the value is know at the time of execution and its value does not change during the execution (eg.qornin the examples above).avectoris a multi-dimensional variable that appears in assigments and it is used to store computation results (eg.ain the examples above).We will discuss more about the restrictions each variable has in order to extract the dependecies and rewrite the loops later in this section.The for loop statement syntax is:FORvar_idxFROMlb_constTOub_expr{loop_body}where:var_idxis the name of the variable representing the loop's index (eg.i,jork).lb_constis a positive constant integer (eg.0,1or42) representing the initial value of the index variable.ub_expris an expression representing the last value of the index variable. There are 3 possible kind of expression that can be written:a positive constant integera parameter namea two-term expression with a parameter name, an operator (+or-) and a constant integer (eg.n-2).loop_bodyare other statements (possibly for loops) contained in the loop body. If there are nested loops, the loops must be perfectly nested, which means that no other statements must be present in the loop body containing another for loop.NOTE: each index variable name must be unique for every for loop.The assigment statement syntax is:STMleft_term=right_term;where:left_termis a vector expression, indexed by one or more index variables (eg.a[i]). A two-term expression can be present as index, but it must contain only one index variable, an operator (+or-) and a constant integer (eg.a[i+2]).right_termis a general expression containing vectors, parameters, indexes and constants.NOTE: please note the;at the end of the statement.The vector expression syntax is:vector_name[index_expr]...where:vector_nameis the name of the vector (eg.a)index_expris an expression containing only one index variable (eg.i,i+1,i-2, etc...). There can be multiple indexes expression, one for each vector's dimension.For example, a correct vector expression isa[i+1][j-1]and an incorrect vector expression isa[i+j][j]becauseiandjare both present in the first dimension index of the vector.Also, vectors with the same name cannot be indexed with different index variables or in different order. For example, ifx[i][j]is present,x[j][i]cannot be present as well asx[i].A general expression is a list of other expression with an operator between each of them. For example,a + 1is an expression of two termsaand1with an operator+in between them. In general, an expression can be described by the following syntax:expr[opother_expr]...where:exprandother_exprare expressionsopis an operator. Supported operators are+(addition),-(subtraction),*(multiplication) and/(division).There can be also round brackets()around an expression.An example of a correct expression is:a + v[i] * (1 - v[i-1]) / 3.0ExamplesLet's look at a simplified relaxation algorithm also used as an example inThe Parallel Execution of DO Loops.
The algorithm takes every point of an input matrixuof sizeMbyNand compute its value as the mean of the adjacent points' value. This computation is doneLtimes.We can describe this algorithm with the following pseudocode:FOR i FROM 1 TO L {
    FOR j FROM 2 TO M-1 {
        FOR k FROM 2 TO N-1 {
            STM u[j][k] = (u[j+1][k] + u[j][k+1] + u[j-1][k] + u[j][k-1]) * 0.25;
        }
    }
}supposing that the matrixuis accessed with one-based index fashion.If we letopolyrewrite this code, we will get this (in C syntax with OMP directives):for(intnew_i=4;new_i<=2*L+M+N-2;new_i++){intnew_j_lb=fmax(1,ceil((1.0/2.0)*(-M-N+new_i+2)));intnew_j_ub=fmin(L,floor((1.0/2.0)*(new_i-2)));#pragma omp parallel forfor(intnew_j=new_j_lb;new_j<=new_j_ub;new_j++){intnew_k_lb=fmax(1,-M+new_i-2*new_j+1);intnew_k_ub=fmin(N-1,new_i-2*new_j-1);for(intnew_k=new_k_lb;new_k<=new_k_ub;new_k++){inti=new_j;intj=new_i-2*new_j-new_k;intk=new_k;u[j][k]=(u[j+1][k]+u[j][k+1]+u[j-1][k]+u[j][k-1])*0.25;}}}Let's look at another example. We have a computation involving three one-dimensional vectorsa,bandcthat needs to be doneqtimes. Each vector is containingnvalues (zero-based indexing). We iterate through eachifrom1ton-2and we compute the following three statements:firstly, we compute the sum ofb[i+1]andc[i]and assign it toa[i].then, we compute the inverse ofa[i]and assign it toa[i-1].lastly, we updateb[i]to the value ofa[i]There are a lot of dependecies in this code, but we don't have to worry sinceopolywill do the job of detecting them for us. We only need to write the pseudocode for this algorithm:FOR k FROM 1 TO q {
    FOR i FROM 1 TO n-2 {
        STM a[i] = b[i+1] + c[i];
        STM a[i-1] = 1.0 / a[i];
        STM b[i] = a[i];
    }
}And the resulting code will be:for(intnew_k=3;new_k<=n+2*q-2;new_k++){intnew_i_lb=fmax(1,ceil((1.0/2.0)*(-n+new_k+2)));intnew_i_ub=fmin(q,floor((1.0/2.0)*(new_k-1)));#pragma omp parallel forfor(intnew_i=new_i_lb;new_i<=new_i_ub;new_i++){intk=new_i;inti=-2*new_i+new_k;a[i]=b[i+1]+c[i];a[i-1]=1.0/a[i];b[i]=a[i];}}