unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Paul Pogonyshev <pogonyshev@gmail.com>
To: Emacs developers <emacs-devel@gnu.org>
Subject: Re: Make computational threads leave user interface usable
Date: Wed, 1 Nov 2017 16:10:27 +0100	[thread overview]
Message-ID: <CAG7BpaqPpj17X8sVUrDeyFhbCKUhpB3KKciCQCWH1phbeQiuwg@mail.gmail.com> (raw)
In-Reply-To: <CAG7Bpar+=Zc9yn6ss2vKnuMCPvJ4bEZ8bJYKb_r1YrKUwgN0Vg@mail.gmail.com>

[-- 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 *);

  reply	other threads:[~2017-11-01 15:10 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-11-01 15:06 Make computational threads leave user interface usable Paul Pogonyshev
2017-11-01 15:10 ` Paul Pogonyshev [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAG7BpaqPpj17X8sVUrDeyFhbCKUhpB3KKciCQCWH1phbeQiuwg@mail.gmail.com \
    --to=pogonyshev@gmail.com \
    --cc=emacs-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).