* Make computational threads leave user interface usable @ 2017-11-01 15:06 Paul Pogonyshev 2017-11-01 15:10 ` Paul Pogonyshev ` (2 more replies) 0 siblings, 3 replies; 10+ messages in thread From: Paul Pogonyshev @ 2017-11-01 15:06 UTC (permalink / raw) To: Emacs developers Since several versions Emacs has Lisp threads, but they are not used much, because 1) only one thread executes at any given time; 2) threads yield control to each other only with explicit (thread-yield) call or IO blocks. Which means that it is pointless to start a new thread for heavy computation: it will lock UI until finished anyway. Attached patch tries to solve point 2 only by making threads automatically yield control to each other from time to time. The patch is mainly for discussion. To see its effect, evaluate this expression: (make-thread (lambda () (dotimes (n 10000000) (when (= (% n 1000000) 0) (message "%s" n))) (message "done"))) In normal Emacs, UI is frozen until the thread completes. You see messages in the echo area, but that's rather a special case: you cannot e.g. navigate or type in the current buffer. With the patch, however, computation thread periodically (and automatically: no alteration of the expression is needed) yields to UI thread, leaving Emacs responsive while computation is going on. There are some problems, though. * Computation is 3-4 times slower than without auto-yielding. You can compare to unpatched Emacs or bind `thread-inhibit-auto-yield' to t in the thread function. This is probably due to the fact it auto-yields ~50 times per second. But on the other hand, does it really have to be that slow? I don't know much about Emacs internals, maybe someone with more knowledge can say if it is unavoidable, or yielding is just not optimized because it is just not done that frequently currently. * Message buffer contents seems screwed. But this is probably "normal", as non-main threads shouldn't touch UI as I understand. This expression is just an example. * Variable `thread-auto-yield-after' is accessible from Lisp, but rebinding doesn't take effect immediately. Which is especially bad if you rebind from nil to a non-nil value. In general, what are the thoughts about the patch? Does it look interesting, or is auto-yielding simply out of question? Paul P.S. Please CC me on replies, I'm not subscribed to the list. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Make computational threads leave user interface usable 2017-11-01 15:06 Make computational threads leave user interface usable Paul Pogonyshev @ 2017-11-01 15:10 ` Paul Pogonyshev 2017-11-01 18:12 ` John Wiegley 2017-11-01 19:10 ` Noam Postavsky 2 siblings, 0 replies; 10+ messages in thread From: Paul Pogonyshev @ 2017-11-01 15:10 UTC (permalink / raw) To: Emacs developers [-- Attachment #1: Type: text/plain, Size: 2393 bytes --] And, of course, I forgot to attach the patch. Paul On 1 November 2017 at 16:06, Paul Pogonyshev <pogonyshev@gmail.com> wrote: > Since several versions Emacs has Lisp threads, but they are not used > much, because 1) only one thread executes at any given time; 2) > threads yield control to each other only with explicit (thread-yield) > call or IO blocks. Which means that it is pointless to start a new > thread for heavy computation: it will lock UI until finished anyway. > > Attached patch tries to solve point 2 only by making threads > automatically yield control to each other from time to time. The patch > is mainly for discussion. > > To see its effect, evaluate this expression: > > (make-thread (lambda () > (dotimes (n 10000000) > (when (= (% n 1000000) 0) > (message "%s" n))) > (message "done"))) > > In normal Emacs, UI is frozen until the thread completes. You see > messages in the echo area, but that's rather a special case: you > cannot e.g. navigate or type in the current buffer. > > With the patch, however, computation thread periodically (and > automatically: no alteration of the expression is needed) yields to UI > thread, leaving Emacs responsive while computation is going on. > > There are some problems, though. > > * Computation is 3-4 times slower than without auto-yielding. You can > compare to unpatched Emacs or bind `thread-inhibit-auto-yield' to t in > the thread function. This is probably due to the fact it auto-yields > ~50 times per second. But on the other hand, does it really have to be > that slow? I don't know much about Emacs internals, maybe someone with > more knowledge can say if it is unavoidable, or yielding is just not > optimized because it is just not done that frequently currently. > > * Message buffer contents seems screwed. But this is probably > "normal", as non-main threads shouldn't touch UI as I understand. This > expression is just an example. > > * Variable `thread-auto-yield-after' is accessible from Lisp, but > rebinding doesn't take effect immediately. Which is especially bad if > you rebind from nil to a non-nil value. > > In general, what are the thoughts about the patch? Does it look > interesting, or is auto-yielding simply out of question? > > Paul > > P.S. Please CC me on replies, I'm not subscribed to the list. [-- Attachment #2: auto-yielding.diff --] [-- Type: text/plain, Size: 6917 bytes --] diff --git a/src/bytecode.c b/src/bytecode.c index 50c7abe289..d52fab43d2 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -403,6 +403,9 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth, if (BYTE_CODE_SAFE && ! (stack_base <= top && top < stack_lim)) emacs_abort (); + if (auto_yield_pending && !thread_inhibit_auto_yield) + Fthread_yield (); + #ifdef BYTE_CODE_METER int prev_op = this_op; this_op = op = FETCH; diff --git a/src/eval.c b/src/eval.c index 063deb4ba0..43a6fca2ff 100644 --- a/src/eval.c +++ b/src/eval.c @@ -2136,6 +2136,8 @@ eval_sub (Lisp_Object form) return form; maybe_quit (); + if (auto_yield_pending && !thread_inhibit_auto_yield) + Fthread_yield (); maybe_gc (); diff --git a/src/systhread.c b/src/systhread.c index 6f9baabaf2..cc0f7e0127 100644 --- a/src/systhread.c +++ b/src/systhread.c @@ -53,6 +53,12 @@ sys_cond_wait (sys_cond_t *c, sys_mutex_t *m) { } +bool +sys_cond_timedwait (sys_cond_t *c, sys_mutex_t *m, const struct timespec *t) +{ + return false; +} + void sys_cond_signal (sys_cond_t *c) { @@ -88,6 +94,7 @@ sys_thread_yield (void) #elif defined (HAVE_PTHREAD) +#include <errno.h> #include <sched.h> #ifdef HAVE_SYS_PRCTL_H @@ -124,6 +131,12 @@ sys_cond_wait (sys_cond_t *cond, sys_mutex_t *mutex) pthread_cond_wait (cond, mutex); } +bool +sys_cond_timedwait (sys_cond_t *cond, sys_mutex_t *mutex, const struct timespec *abstime) +{ + return pthread_cond_timedwait (cond, mutex, abstime) == ETIMEDOUT; +} + void sys_cond_signal (sys_cond_t *cond) { @@ -269,6 +282,14 @@ sys_cond_wait (sys_cond_t *cond, sys_mutex_t *mutex) EnterCriticalSection ((LPCRITICAL_SECTION)mutex); } +bool +sys_cond_timedwait (sys_cond_t *cond, sys_mutex_t *mutex, const struct timespec *abstime) +{ + /* FIXME: Write. */ + sys_cond_wait (cond, mutex); + return false; +} + void sys_cond_signal (sys_cond_t *cond) { diff --git a/src/systhread.h b/src/systhread.h index 443dc55c6a..075207efec 100644 --- a/src/systhread.h +++ b/src/systhread.h @@ -19,6 +19,8 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #ifndef SYSTHREAD_H #define SYSTHREAD_H +#include <time.h> + #ifdef THREADS_ENABLED #ifdef HAVE_PTHREAD @@ -95,6 +97,7 @@ extern void sys_mutex_unlock (sys_mutex_t *); extern void sys_cond_init (sys_cond_t *); extern void sys_cond_wait (sys_cond_t *, sys_mutex_t *); +extern bool sys_cond_timedwait (sys_cond_t *, sys_mutex_t *, const struct timespec *); extern void sys_cond_signal (sys_cond_t *); extern void sys_cond_broadcast (sys_cond_t *); extern void sys_cond_destroy (sys_cond_t *); diff --git a/src/thread.c b/src/thread.c index c03cdda0fa..dcddd6d63d 100644 --- a/src/thread.c +++ b/src/thread.c @@ -29,6 +29,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ static struct thread_state main_thread; struct thread_state *current_thread = &main_thread; +bool auto_yield_pending = false; static struct thread_state *all_threads = &main_thread; @@ -37,6 +38,12 @@ static sys_mutex_t global_lock; extern int poll_suppress_count; extern volatile int interrupt_input_blocked; +static int auto_yield_thread_started; +static sys_thread_t auto_yield_thread; +static sys_mutex_t auto_yield_lock; +static sys_cond_t auto_yield_rules_changed; +static bool auto_yield_thread_switched; + \f /* m_specpdl is set when the thread is created and cleared when the @@ -65,6 +72,9 @@ post_acquire_global_lock (struct thread_state *self) if (prev_thread != current_thread) { + if (auto_yield_thread_started) + sys_mutex_lock (&auto_yield_lock); + /* PREV_THREAD is NULL if the previously current thread exited. In this case, there is no reason to unbind, and trying will crash. */ @@ -72,6 +82,14 @@ post_acquire_global_lock (struct thread_state *self) unbind_for_thread_switch (prev_thread); rebind_for_thread_switch (); + if (auto_yield_thread_started) + { + auto_yield_pending = false; + auto_yield_thread_switched = true; + sys_cond_signal (&auto_yield_rules_changed); + sys_mutex_unlock (&auto_yield_lock); + } + /* Set the new thread's current buffer. This needs to be done even if it is the same buffer as that of the previous thread, because of thread-local bindings. */ @@ -747,6 +765,38 @@ run_thread (void *state) return NULL; } +static void * +auto_yielder (void *unused) +{ + sys_mutex_lock (&auto_yield_lock); + + while (true) + { + int timed_out = 0; + auto_yield_thread_switched = false; + + if (auto_yield_pending || NILP (Vthread_auto_yield_after)) + sys_cond_wait (&auto_yield_rules_changed, &auto_yield_lock); + else + { + double after = extract_float (Vthread_auto_yield_after); + + if (after > 0) + { + struct timespec until = timespec_add (current_timespec (), dtotimespec (after)); + timed_out = sys_cond_timedwait (&auto_yield_rules_changed, &auto_yield_lock, &until); + } + else + sys_cond_wait (&auto_yield_rules_changed, &auto_yield_lock); + } + + if (timed_out && !auto_yield_thread_switched) + auto_yield_pending = true; + } + + return NULL; +} + void finalize_one_thread (struct thread_state *state) { @@ -815,6 +865,10 @@ If NAME is given, it must be a string; it names the new thread. */) /* FIXME: race here where new thread might not be filled in? */ XSETTHREAD (result, new_thread); + + if (!auto_yield_thread_started) + auto_yield_thread_started = sys_thread_create (&auto_yield_thread, "auto-yield", auto_yielder, NULL); + return result; } @@ -1024,10 +1078,20 @@ init_threads_once (void) void init_threads (void) { + DEFVAR_LISP ("thread-auto-yield-after", Vthread_auto_yield_after, + doc: /* Make current thread yield automatically after this many seconds. */); + Vthread_auto_yield_after = make_float (0.02); + + DEFVAR_BOOL ("thread-inhibit-auto-yield", thread_inhibit_auto_yield, + doc: /* Non-nil means never auto-yield. */); + thread_inhibit_auto_yield = false; + init_main_thread (); sys_cond_init (&main_thread.thread_condvar); sys_mutex_init (&global_lock); sys_mutex_lock (&global_lock); + sys_mutex_init (&auto_yield_lock); + sys_cond_init (&auto_yield_rules_changed); current_thread = &main_thread; main_thread.thread_id = sys_thread_self (); } diff --git a/src/thread.h b/src/thread.h index 19baafbf8a..d5f76d0e05 100644 --- a/src/thread.h +++ b/src/thread.h @@ -293,6 +293,7 @@ XCONDVAR (Lisp_Object a) } extern struct thread_state *current_thread; +extern bool auto_yield_pending; extern void finalize_one_thread (struct thread_state *state); extern void finalize_one_mutex (struct Lisp_Mutex *); ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: Make computational threads leave user interface usable 2017-11-01 15:06 Make computational threads leave user interface usable Paul Pogonyshev 2017-11-01 15:10 ` Paul Pogonyshev @ 2017-11-01 18:12 ` John Wiegley 2017-11-01 20:03 ` Eli Zaretskii 2017-11-01 21:19 ` Paul Pogonyshev 2017-11-01 19:10 ` Noam Postavsky 2 siblings, 2 replies; 10+ messages in thread From: John Wiegley @ 2017-11-01 18:12 UTC (permalink / raw) To: Paul Pogonyshev; +Cc: Emacs developers >>>>> "PP" == Paul Pogonyshev <pogonyshev@gmail.com> writes: PP> Since several versions Emacs has Lisp threads, but they are not used much, PP> because 1) only one thread executes at any given time; 2) threads yield PP> control to each other only with explicit (thread-yield) call or IO blocks. PP> Which means that it is pointless to start a new thread for heavy PP> computation: it will lock UI until finished anyway. I wouldn't say it's pointless, just that it calls for a programming regimen where you add (thread-yield) calls at appropriate places. PP> Attached patch tries to solve point 2 only by making threads PP> automatically yield control to each other from time to time. The patch PP> is mainly for discussion. This would introduce the sort of indeterminacy that raised so much objection to threading support last year. I'm fairly opposed to pre-emptive threading until we've heard from the field about the success of green threading. (make-thread (lambda () (dotimes (n 10000000) (when (= (% n 1000000) 0) (message "%s" n))) (message "done"))) I think the problem here is not with Emacs' threading model, but how you've written the code. It is currently a _feature_ that control is not taken away while engaged in such a computation. For example, this allows delicate state manipulation without interference from other threads. If periodic suspension were desired, you could have written: (make-thread (lambda () (dotimes (n 10000000) (thread-yield) (when (= (% n 1000000) 0) (message "%s" n))) (message "done"))) This gives the UI access to Emacs again, but otherwise keeps executing if no other threads need control. -- John Wiegley GPG fingerprint = 4710 CF98 AF9B 327B B80F http://newartisans.com 60E1 46C4 BD1A 7AC1 4BA2 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Make computational threads leave user interface usable 2017-11-01 18:12 ` John Wiegley @ 2017-11-01 20:03 ` Eli Zaretskii 2017-11-01 21:19 ` Paul Pogonyshev 1 sibling, 0 replies; 10+ messages in thread From: Eli Zaretskii @ 2017-11-01 20:03 UTC (permalink / raw) To: John Wiegley; +Cc: pogonyshev, emacs-devel > From: "John Wiegley" <johnw@gnu.org> > Date: Wed, 01 Nov 2017 11:12:02 -0700 > Cc: Emacs developers <emacs-devel@gnu.org> > > This would introduce the sort of indeterminacy that raised so much objection > to threading support last year. I'm fairly opposed to pre-emptive threading > until we've heard from the field about the success of green threading. I agree. I would even say it more bluntly: I don't believe the current threading code can be extended to pre-emptive threads so easily. Much more redesign and reimplementing of several core internals will be needed before it will become possible to write reliable programs with pre-emptive threading. How do I know? by the amount of gray hair we needed to acquire and the amount of sweat and blood we invested before the current much simpler model got to what we have now, which is still not perfect even for the limited features it was supposed to support. > (make-thread (lambda () > (dotimes (n 10000000) > (thread-yield) > (when (= (% n 1000000) 0) > (message "%s" n))) > (message "done"))) > > This gives the UI access to Emacs again, but otherwise keeps executing if no > other threads need control. I think we currently lack intermediate infrastructure to make writing such programs easily. Right now, a Lisp programmer who wants to take advantage of these features needs to jump through many hoops and overcome some counter-intuitive/surprising obstacles and issues. Volunteers, where are you? ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Make computational threads leave user interface usable 2017-11-01 18:12 ` John Wiegley 2017-11-01 20:03 ` Eli Zaretskii @ 2017-11-01 21:19 ` Paul Pogonyshev 2017-11-01 21:47 ` John Wiegley 2017-11-03 15:50 ` Stefan Monnier 1 sibling, 2 replies; 10+ messages in thread From: Paul Pogonyshev @ 2017-11-01 21:19 UTC (permalink / raw) To: Paul Pogonyshev, Emacs developers > I wouldn't say it's pointless, just that it calls for a programming regimen > where you add (thread-yield) calls at appropriate places. What if this auto-yield is made optional? It sort of already is in the patch, but the default value could be made nil. Then, you can activate it downstream by using (let ((thread-auto-yield-after ...) ...), but normally it is not active. So, activating becomes your own decision, not something sneaked upon you. Peppering code with (thread-yield) has downsides in that it requires to write every single piece of code you might at some point run in a separate thread with that in mind. And not everything is as simple as one loop with many iterations. And if you fail to add (thread-yield) at reasonable intervals, it will still lead to unresponsive UI that sometimes works smoothly, yet at other times freezes for a couple of seconds. I think it would be very useful to be able to run sort-of-arbitrary computation without freezing UI. (Sort-of, because it shouldn't interfere with UI, but otherwise can compute whatever it wants). Imagine e.g. running 5000 ERT tests, and still continuing to work with Emacs as normal. Paul ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Make computational threads leave user interface usable 2017-11-01 21:19 ` Paul Pogonyshev @ 2017-11-01 21:47 ` John Wiegley 2017-11-03 15:50 ` Stefan Monnier 1 sibling, 0 replies; 10+ messages in thread From: John Wiegley @ 2017-11-01 21:47 UTC (permalink / raw) To: Paul Pogonyshev; +Cc: Emacs developers >>>>> "PP" == Paul Pogonyshev <pogonyshev@gmail.com> writes: PP> What if this auto-yield is made optional? It sort of already is in the PP> patch, but the default value could be made nil. Then, you can activate it PP> downstream by using (let ((thread-auto-yield-after ...) ...), but normally PP> it is not active. So, activating becomes your own decision, not something PP> sneaked upon you. Here's what I would accept: A macro to inject (thread-yield) calls into function definitions. This is extremely low-impact (just a new macro), works with the existing implementation, and offers the semantics you're asking for in a way that's good enough for now. Of course, it only works for code defined using the macro, but I actually don't _want_ a patch that changes code written to be cooperative into code that evaluates pre-emptively. If, on the other hand, you want to define a set of thread functions that allow full pre-emption by auto-injecting yield points, then I believe such a macro would serve this need. -- John Wiegley GPG fingerprint = 4710 CF98 AF9B 327B B80F http://newartisans.com 60E1 46C4 BD1A 7AC1 4BA2 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Make computational threads leave user interface usable 2017-11-01 21:19 ` Paul Pogonyshev 2017-11-01 21:47 ` John Wiegley @ 2017-11-03 15:50 ` Stefan Monnier 1 sibling, 0 replies; 10+ messages in thread From: Stefan Monnier @ 2017-11-03 15:50 UTC (permalink / raw) To: emacs-devel > What if this auto-yield is made optional? The problem is that the megabytes of C and Lisp code out there all assume that there's only a single thread running. This assumption is so strong that we don't even really provide the synchronization primitives necessary to make real concurrent programming usable (e.g. locks, transactions, ...). So such an option would be similar to a "please corrupt my Emacs state in random ways" option. As you've seen context switches are currently very expensive (they undo all the dynamic-bindings of the source thread and reinstall the dynamic-bindings of the destination thread). It's probably a good idea to start thinking about how to add real concurrency to Emacs, but it needs to start by looking at how we can make sure the concurrent threads only share read-only data (plus some extra shared state properly protected via some transactional system, or maybe just no shared state but some messaging mechanism instead). That requires a serious design effort on the Elisp side, with a good understanding of the current C code, plus some deep changes at the C level. Stefan ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Make computational threads leave user interface usable 2017-11-01 15:06 Make computational threads leave user interface usable Paul Pogonyshev 2017-11-01 15:10 ` Paul Pogonyshev 2017-11-01 18:12 ` John Wiegley @ 2017-11-01 19:10 ` Noam Postavsky 2017-11-01 20:16 ` Eli Zaretskii 2 siblings, 1 reply; 10+ messages in thread From: Noam Postavsky @ 2017-11-01 19:10 UTC (permalink / raw) To: Paul Pogonyshev; +Cc: Emacs developers On Wed, Nov 1, 2017 at 11:06 AM, Paul Pogonyshev <pogonyshev@gmail.com> wrote: > Since several versions Emacs has Lisp threads, Um, not even a single version of Emacs has been released with Lisp threads. > In general, what are the thoughts about the patch? Does it look > interesting, or is auto-yielding simply out of question? Seems a bit premature, given we still have stuff like #25214 "Interacting with user from threads other than the primary" and #25171 "Error in thread causes Emacs to abort" unresolved. https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25214 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25171 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Make computational threads leave user interface usable 2017-11-01 19:10 ` Noam Postavsky @ 2017-11-01 20:16 ` Eli Zaretskii 2017-11-01 20:26 ` Noam Postavsky 0 siblings, 1 reply; 10+ messages in thread From: Eli Zaretskii @ 2017-11-01 20:16 UTC (permalink / raw) To: Noam Postavsky; +Cc: pogonyshev, emacs-devel > From: Noam Postavsky <npostavs@users.sourceforge.net> > Date: Wed, 1 Nov 2017 15:10:10 -0400 > Cc: Emacs developers <emacs-devel@gnu.org> > > Seems a bit premature, given we still have stuff like #25214 > "Interacting with user from threads other than the primary" and #25171 > "Error in thread causes Emacs to abort" unresolved. > > https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25214 > https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25171 Does 25171 still happen? Because I committed the part of the patch I thought was a good idea back then. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Make computational threads leave user interface usable 2017-11-01 20:16 ` Eli Zaretskii @ 2017-11-01 20:26 ` Noam Postavsky 0 siblings, 0 replies; 10+ messages in thread From: Noam Postavsky @ 2017-11-01 20:26 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Paul Pogonyshev, Emacs developers tag 25171 fixed close 25171 quit On Wed, Nov 1, 2017 at 4:16 PM, Eli Zaretskii <eliz@gnu.org> wrote: > Does 25171 still happen? Because I committed the part of the patch I > thought was a good idea back then. Hmm, maybe not, I had already asked on its merged bug [1] and there was no response. Given that we haven't seen any additional similar reports, we can probably consider it solved. [1]: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=25164#28 ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2017-11-03 15:50 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2017-11-01 15:06 Make computational threads leave user interface usable Paul Pogonyshev 2017-11-01 15:10 ` Paul Pogonyshev 2017-11-01 18:12 ` John Wiegley 2017-11-01 20:03 ` Eli Zaretskii 2017-11-01 21:19 ` Paul Pogonyshev 2017-11-01 21:47 ` John Wiegley 2017-11-03 15:50 ` Stefan Monnier 2017-11-01 19:10 ` Noam Postavsky 2017-11-01 20:16 ` Eli Zaretskii 2017-11-01 20:26 ` Noam Postavsky
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/emacs.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).