[m-users.] How to write stack-friendly mercury-code?
Paul Bone
paul at bone.id.au
Mon Mar 28 21:51:02 AEDT 2016
On Mon, Mar 28, 2016 at 11:51:52AM +0200, Dirk Ziegemeyer wrote:
> Hi,
>
> when I increase the data volume that is processed by a mercury application, the windows-version (asm_fast.gc/i686-w64-mingw32.static) stops at runtime with this error message:
>
> *** Mercury runtime: caught segmentation violation ***
> This may have been caused by a stack overflow, due to unbounded recursion.
> exiting from signal handler
>
> The Mac OS X version (hlc.gc/x86_64-apple-darwin14.5.0) still works.
>
> Up to now, I did not care at all about stack consumption. Now, I’d like to learn how to write stack-friendly mercury code in order to avoid overflows at runtime.
>
> To iterate over data, the application uses predicates from the mercury library like list.foldl2, list.map, map.foldl and list.append.
>
> Thank you in advance for any guidance on writing robust mercury code.
>
Hi Dirk, There's a couple of things you can do.
+ Make code tail recursive:
This is pretty obvious but I've included it because how to do this is
not so obvious.
This is tail recursive:
foldl(_, [], !Acc).
foldl(P, [X | Xs], !Acc) :-
P(X, !Acc),
foldl(P, Xs, !Acc).
But this is not, even though the last call is a recursive call (unlike
Prolog).
map(_, [], []).
map(P, [X | Xs], [Y | Ys]) :-
P(X, Y),
map(P, Xs, Ys).
The construction of [Y | Ys] executes after the recursive call. So the
recursive call cannot be a tail call (see LCMC below for now to fix
this). So code can _look_ tail recursive that isn't. Lots of code can
be transformed into something that uses an accumulator like foldl. This
usually trades stack space for heap space. So it's often easier just to
add more stack space.
This also isn't tail recursive, X and Y are both outputs:
foo(..., X, Y) :-
...,
foo(..., Y, X).
Even though there is no construction after the recursive call, the
outputs must be placed in different registers because of their
respective order. So work needs to be done after the recursive call,
therefore the recursive call isn't a tail call.
+ Reduce the amount of stack space used.
This is a neat trick. If one loop can be easily refactored into two
loops with a depth counter then the number of stack frames used at any
given time can be halved.
foo(...).
foo(...) :-
foo_inner(1000, ...),
foo(...).
foo_inner(N, ...) :-
( if N = 0 then
true % continue with the outer loop, removing the inner 1000
stack frames
else
...,
foo_inner(N - 1, ...).
).
Pick a constant value that only just fits within stack.
+ Enable --last-call-modulo-recursion optimisation (LCMC).
This will make map tail recursive by performing the construction [Y | Ys]
before the recursive call, and filling in the value Ys in this term
within the recursive call.
+ Use a segmented stack
If you use --stack-segments (an stseg grade component, compatible with
low level C grades) then a segmented stack will be used, a segmented
stack grows and shrinks as necessary. Unbounded recursion can now cause
your computer to thrash, rather than just crashing your program. This
is the easiest option, but it doesn't reduce your program's memory usage.
It's a good option if the limit is the size of the stack, and not how
much RAM you have.
+ Increase the size of the stack.
In low-level graces, like asm_fast, put MERCURY_OPTIONS="--detstack-size
N" in your environment. Check the Mercury user's guide.
I've been working on some changes to Mercury that will make it easier for
developers to find which parts of their program are potentially causing
crashes due to stack exhaustion.
Cheers.
--
Paul Bone
More information about the users
mailing list