Re: Excessive copies of set elements in GMPL

Domingo Alvarez Duarte <[email protected]> Thu, 16 Jul 2020 19:21:26 +0200
Newsgroups gmane.comp.gnu.glpk
Message-ID <[email protected]>
I also add a splaytree to replace the avltree on ARRAY in GMPL and the 
memory usage was reduced even more and the processing time now is 1/2 of 
the original.

See 
https://github.com/mingodad/GLPK/commit/7ffe6eae90e25d23e04b6c2f612daae03f60a8a7

====

/usr/bin/time ./glpsol -m sum-test.mod #with refcount & splaytree
GLPSOL: GLPK LP/MIP Solver, v4.65
...

Memory used: 169.0 Mb (177220286 bytes)
1.76user 0.09system 0:01.85elapsed 100%CPU (0avgtext+0avgdata 
176188maxresident)k

====

On 16/7/20 18:40, Domingo Alvarez Duarte wrote:
> Hello Andrew !
>
> Thank you for reply !
>
> After doing some experimentation I think I found an easy way to 
> eliminate unnecessary set copies here is a commit on 
> https://github.com/mingodad/GLPK/commit/6f3da6ab31ca8d710f706f60635e5b17cf2ded40, 
> also see bellow, glpsol now uses 1/3 of the memory and is slight faster:
>
> ====
>
> set S := {1..1000000};
> param d { i in S} := if ((i mod 2) == 0) then 0.0000000123 else 
> 12345.0000000001;
> display card(S);
> display sum {i in S} d[i];
> display card(S)*2;
> display card(S)*3;
> display sum {i in S : (i mod 2) == 0} d[i];
> display sum {i in S : (i mod 2) <> 0} d[i];
> end;
>
> ====
>
> /usr/bin/time ./glpsol -m sum-test.mod #without refcount
> GLPSOL: GLPK LP/MIP Solver, v4.65
> ...
>
> Memory used: 645.0 Mb (676328774 bytes)
> 3.68user 0.23system 0:03.91elapsed 100%CPU (0avgtext+0avgdata 
> 664568maxresident)k
>
> ====
>
> /usr/bin/time ./glpsol -m sum-test.mod #with refcount
> GLPSOL: GLPK LP/MIP Solver, v4.65
> ...
>
> Memory used: 215.4 Mb (225829958 bytes)
> 3.06user 0.08system 0:03.14elapsed 100%CPU (0avgtext+0avgdata 
> 223772maxresident)k
>
> ====
>
> diff --git a/src/mpl/mpl.h b/src/mpl/mpl.h
> index ddd3154..262a783 100644
> --- a/src/mpl/mpl.h
> +++ b/src/mpl/mpl.h
> @@ -1358,6 +1358,8 @@ struct ARRAY
>           the array is 0-dimensional */
>        int size;
>        /* size of the array, i.e. number of its members */
> +      int refcount;
> +      /* reference counting to allow reuse without copy */
>        MEMBER *head;
>        /* the first array member; NULL means the array is empty */
>        MEMBER *tail;
> diff --git a/src/mpl/mpl3.c b/src/mpl/mpl3.c
> index 2489db2..036ef8c 100644
> --- a/src/mpl/mpl3.c
> +++ b/src/mpl/mpl3.c
> @@ -1002,6 +1002,7 @@ MEMBER *add_tuple
>        xassert(set != NULL);
>        xassert(set->type == A_NONE);
>        xassert(set->dim == tuple_dimen(mpl, tuple));
> +      xassert(set->refcount == 1);
>        memb = add_member(mpl, set, tuple);
>        memb->value.none = NULL;
>        return memb;
> @@ -1038,6 +1039,10 @@ ELEMSET *copy_elemset
>        xassert(set != NULL);
>        xassert(set->type == A_NONE);
>        xassert(set->dim > 0);
> +      if(set->size) {
> +          ++set->refcount;
> +          return set;
> +      }
>        copy = create_elemset(mpl, set->dim);
>        for (memb = set->head; memb != NULL; memb = memb->next)
>           add_tuple(mpl, copy, copy_tuple(mpl, memb->tuple));
> @@ -1170,6 +1175,7 @@ ELEMSET *set_union
>        xassert(X != NULL);
>        xassert(X->type == A_NONE);
>        xassert(X->dim > 0);
> +      xassert(X->refcount == 1); /* only add to recently/owned 
> created sets */
>        xassert(Y != NULL);
>        xassert(Y->type == A_NONE);
>        xassert(Y->dim > 0);
> @@ -1626,6 +1632,7 @@ ARRAY *create_array(MPL *mpl, int type, int dim)
>        array->type = type;
>        array->dim = dim;
>        array->size = 0;
> +      array->refcount = 1;
>        array->head = NULL;
>        array->tail = NULL;
>        array->tree = NULL;
> @@ -1742,6 +1749,7 @@ void delete_array
>  )
>  {     MEMBER *memb;
>        xassert(array != NULL);
> +      if(--array->refcount) return; /* someone is still using this 
> array */
>        /* delete all existing array members */
>        while (array->head != NULL)
>        {  memb = array->head;
>
> ====
>
> Cheers !
>
> On 16/7/20 14:06, Andrew Makhorin wrote:
>>> Trying to improve GLPK/GMPL
>> It is a non-trivial task.
>>
>> The MathProg translator included in glpk was implemented in a
>> non-efficient way, because there was no intention to process models
>> of huge size (so this allowed essentially reducing implementation
>> efforts).
>>
>> The key idea to translate MathProg (or AMPL) models much more
>> efficiently is to avoid using "direct" representation of sets and
>> arrays and implement all operations in a way similar to one used
>> in relational database management systems.
>>
>>> I can see that GMPL model generation is
>>> making an excessive number of set element copies (see bellow).
>> Please note that most of the copies might be temporary quantities
>> that exist (allocated in the memory) only during evaluation of the
>> (sub)expressions involving these quantities.
>>
>>
>> Best regards,
>>
>> Andrew Makhorin