[m-rev.] for review: avoid MR_call_engine in thread.spawn
Peter Wang
wangp at students.csse.unimelb.edu.au
Thu Apr 26 16:41:05 AEST 2007
Pending bootchecks.
Estimated hours taken: 10
Branches: main
Change the implementation of thread.spawn in low-level C grades. Previously it
called the thread goal (a closure) via an exported Mercury procedure. The
problem with that is the call counts as a C -> Mercury call, and so goes via
MR_call_engine() which sets up a large stack frame on the Mercury engine's C
stack that won't be deallocated until the thread terminates.
This is unnecessary inefficient since we can call the thread goal directly
instead of via MR_call_engine(). The thread goal won't be returning to a C
foreign proc when it finishes, and we don't need MR_call_engine() reserving
C stack space for Mercury execution as any Mercury engine that is about to
execute the thread goal would have that reserved space already.
library/thread.m:
As above.
tests/par_conj/Mmakefile:
tests/par_conj/spawn_many.exp:
tests/par_conj/spawn_many.m:
Add test case.
Index: library/thread.m
===================================================================
RCS file: /home/mercury/mercury1/repository/mercury/library/thread.m,v
retrieving revision 1.7
diff -u -r1.7 thread.m
--- library/thread.m 21 Mar 2007 22:30:27 -0000 1.7
+++ library/thread.m 26 Apr 2007 05:17:17 -0000
@@ -80,8 +80,7 @@
#if !defined(MR_HIGHLEVEL_CODE)
MR_Context *ctxt;
ctxt = MR_create_context(""spawn"", MR_CONTEXT_SIZE_REGULAR, NULL);
- ctxt->MR_ctxt_resume =
- MR_ENTRY(mercury__thread__spawn_call_back_to_mercury_cc_multi);
+ ctxt->MR_ctxt_resume = MR_ENTRY(mercury__thread__spawn_begin_thread);
/*
** Store the closure on the top of the new context's stack.
@@ -144,8 +143,8 @@
*/
#ifndef MR_HIGHLEVEL_CODE
- MR_define_extern_entry(
- mercury__thread__spawn_call_back_to_mercury_cc_multi);
+ MR_define_extern_entry(mercury__thread__spawn_begin_thread);
+ MR_declare_label(mercury__thread__spawn_end_thread);
MR_define_extern_entry(mercury__thread__yield_resume);
#endif
").
@@ -154,18 +153,23 @@
"
#ifndef MR_HIGHLEVEL_CODE
+ MR_declare_entry(mercury__do_call_closure_1);
+
MR_BEGIN_MODULE(thread_module)
- MR_init_entry_ai(mercury__thread__spawn_call_back_to_mercury_cc_multi);
+ MR_init_entry_ai(mercury__thread__spawn_begin_thread);
+ MR_init_label(mercury__thread__spawn_end_thread);
MR_init_entry_ai(mercury__thread__yield_resume);
MR_BEGIN_CODE
- MR_define_entry(mercury__thread__spawn_call_back_to_mercury_cc_multi);
+ MR_define_entry(mercury__thread__spawn_begin_thread);
+ {
+ /* Call the closure placed the top of the stack. */
+ MR_r1 = *MR_sp;
+ MR_noprof_call(MR_ENTRY(mercury__do_call_closure_1),
+ MR_LABEL(mercury__thread__spawn_end_thread));
+ }
+ MR_define_label(mercury__thread__spawn_end_thread);
{
- MR_save_registers();
- /*
- ** Get the closure from the top of the stack.
- */
- ML_call_back_to_mercury_cc_multi(*((MR_Word *)MR_sp));
MR_destroy_context(MR_ENGINE(MR_eng_this_context));
MR_ENGINE(MR_eng_this_context) = NULL;
MR_runnext();
Index: tests/par_conj/Mmakefile
===================================================================
RCS file: /home/mercury/mercury1/repository/tests/par_conj/Mmakefile,v
retrieving revision 1.11
diff -u -r1.11 Mmakefile
--- tests/par_conj/Mmakefile 26 Apr 2007 04:39:09 -0000 1.11
+++ tests/par_conj/Mmakefile 26 Apr 2007 05:17:17 -0000
@@ -67,12 +67,24 @@
PROGS0 =
endif
+ifneq "$(findstring par,$(GRADE))" ""
+ # These tests are to do with explicit threads rather than parallel
+ # conjunction, but the tests require multiple engines to be enabled,
+ # which was already set up in this directory.
+ #
+ THREAD_PROGS = \
+ spawn_many
+else
+ THREAD_PROGS =
+endif
+
ifneq "$(findstring decldebug,$(GRADE))" ""
OBJ_PROGS =
PROGS =
else
OBJ_PROGS =
- PROGS = $(PROGS0) $(DEP_PAR_CONJ_PROGS) $(INDEP_PAR_CONJ_PROGS)
+ PROGS = $(PROGS0) $(DEP_PAR_CONJ_PROGS) $(INDEP_PAR_CONJ_PROGS) \
+ $(THREAD_PROGS)
endif
# `mmc --make' doesn't expect subdirectories to appear in targets.
@@ -122,14 +134,22 @@
{ [ -f $*.inp ] && cat $*.inp; } | $(ENGINES) ./$< > $@ 2>&1 || \
{ grep . $@ /dev/null; exit 1; }
+ifeq "$(filter hl% java% il%,$(GRADE))" ""
+
# Run threads_hang with multiple OS threads in lowlevel parallel grades.
# Repeat the test a few times in increase the chances of getting a deadlock.
-ifeq "$(filter hl% java% il%,$(GRADE))" ""
threads_hang.out: threads_hang
for i in 1 2 3 4 5 6 7 8 9 10 ; do \
MERCURY_OPTIONS=-P10 ./threads_hang 2>&1 > threads_hang.out ||\
{ echo 'failed' > threads_hang.out; break; } \
done
+
+# Run spawn_many with smallish C stacks, so we don't need to spawn so many
+# threads to see the bug.
+spawn_many.out: spawn_many
+ ulimit -s 256 && $(ENGINES) ./spawn_many 2>&1 > spawn_many.out || \
+ { grep . $@ /dev/null; exit 1; }
+
endif
#-----------------------------------------------------------------------------#
Index: tests/par_conj/spawn_many.exp
===================================================================
RCS file: tests/par_conj/spawn_many.exp
diff -N tests/par_conj/spawn_many.exp
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ tests/par_conj/spawn_many.exp 26 Apr 2007 05:17:17 -0000
@@ -0,0 +1 @@
+ok
Index: tests/par_conj/spawn_many.m
===================================================================
RCS file: tests/par_conj/spawn_many.m
diff -N tests/par_conj/spawn_many.m
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ tests/par_conj/spawn_many.m 26 Apr 2007 05:17:17 -0000
@@ -0,0 +1,64 @@
+% vim: ft=mercury ts=4 sw=4 et
+% This program spawns many threads very quickly. In an old implementation of
+% thread.spawn, each new Mercury thread would push a large C stack frame on the
+% executing Mercury engine's C stack. When the engine hit a blocking call
+% (e.g. channel.get) it would switch to the next Mercury context waiting, which
+% would usually be the start of another Mercury thread, which pushes another
+% large C stack frame. Soon the C stack would be exhausted.
+%
+:- module spawn_many.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is cc_multi.
+
+:- implementation.
+
+:- import_module int.
+:- import_module list.
+:- import_module maybe.
+:- import_module string.
+:- import_module thread.
+:- import_module thread.channel.
+:- import_module unit.
+
+main(!IO) :-
+ NumSpawn = 5000,
+ channel.init(Channel, !IO),
+ loop(Channel, NumSpawn, !IO),
+ count(Channel, 0, NumExit, !IO),
+ (if NumSpawn = NumExit then
+ io.write_string("ok\n", !IO)
+ else
+ io.format("not ok: %d != %d\n", [i(NumSpawn), i(NumExit)], !IO)
+ ).
+
+:- pred loop(channel(unit)::in, int::in, io::di, io::uo) is cc_multi.
+
+loop(Channel, N, !IO) :-
+ ( if N = 0 then
+ true
+ else
+ thread.spawn((pred(!.IO::di, !:IO::uo) is cc_multi :-
+ foo(Channel, !IO)
+ ), !IO),
+ loop(Channel, N-1, !IO)
+ ).
+
+:- pred foo(channel(unit)::in, io::di, io::uo) is det.
+
+foo(Channel, !IO) :-
+ channel.put(Channel, unit, !IO).
+
+:- pred count(channel(unit)::in, int::in, int::out, io::di, io::uo) is det.
+
+count(Channel, Num0, Num, !IO) :-
+ channel.try_take(Channel, MaybeUnit, !IO),
+ (
+ MaybeUnit = yes(_),
+ count(Channel, Num0 + 1, Num, !IO)
+ ;
+ MaybeUnit = no,
+ Num = Num0
+ ).
--------------------------------------------------------------------------
mercury-reviews mailing list
Post messages to: mercury-reviews at csse.unimelb.edu.au
Administrative Queries: owner-mercury-reviews at csse.unimelb.edu.au
Subscriptions: mercury-reviews-request at csse.unimelb.edu.au
--------------------------------------------------------------------------
More information about the reviews
mailing list