Subject: bug#26710: Fwd: 25.2; project-find-regexp makes emacs use 100% cpu

From: Dmitry Gutov <[email-masked]>
Date: Mon, 1 May 2017 05:42:21 +0300

On 30.04.2017 21:47, Eli Zaretskii wrote:

> I'll try to look at this.  According to my profiling, the lion's share
> of time is taken by xref--collect-matches, so that's the place to try
> concurrency.

I think that's too late. By the time xref--collect-matches is called 
(and it's called for each hit), we've already spent time synchronously 
waiting for the find-grep invocation to finish.

When there are a lot of matches, xref--collect-matches can take some 
significant time, with opening the buffers and everything. That can be 
optimized, however, as a separate issue, and I don't think there's 
anything to parallelize there, since it all happens in Elisp.

What we _can_ manage to run in parallel, in the find-grep process in the 
background, and the post-processing of the results in Elisp. Depending 
on how matches there are in total, compared to the project size (which 
affects how long find-grep will run), the second part will still affect 
the responsiveness of the UI to a smaller or larger degree, but 
ultimately there's no way around this AFAIK, as long as Elisp threads do 
not provide parallelism.

>> - Being able to hook up in an asynchronous fashion to that sequence in a
>> (second?) background thread, to render the search results in the xref
>> buffer as soon as they appear.
> 
> For the other thread to be background, it will need to yield from time
> to time, otherwise the user experience will be identical to what we
> have today, because an un-yielding thread will hold the execution unit
> until it does its job completely, and no other thread gets to run
> until it does.

Here's how I imagine it:

- Main thread creates a thread P which invoked the find-grep, somehow 
creates a "generator" object, yields to the main thread.

- The main thread creates the "other thread" which creates a buffer for 
displaying the xref items and marks it as still loading (e.g. with a 
spinner in the mode-line). And then repeatedly calls the generator for 
the next element. There are three choices:

1. An element is returned. Render it into the buffer.
2. No next element available. That should automatically yield from the 
current thread until it becomes available. That kind of logic should 
reside somewhere inside the generator, along with storing the reference 
to the current thread, to resume it later.
3. No more items, the process in P is finished. Mark the xref buffer as 
completed.

The things I'm not clear on are:

- How to best "convert" the process buffer into a generator object.

- Which generator type to use. Not sure if any of the ones we already 
have (generator.el, or iterator.el and stream.el in ELPA) will help.

- If the thread P is needed at all, or we could just one the main one 
instead of it.

- Whether we should forget all that concurrency nonsense (at least for 
this problem), and instead of a generator go with callbacks, similar to 
the API of the dir-status-files VC command. This way, each callback will 
render a new line, and the last one will mark the buffer as completed.