This is an archive of the discontinued LLVM Phabricator instance.

[lldb] Rate limit progress reports -- different approach [WIP-ish]
Needs ReviewPublic

Authored by labath on Jun 7 2023, 4:43 AM.

Details

Summary

Have the Progress class spawn a thread to periodically send progress
reports.

The reporting period could be made configurable, but for now I've
hardcoded it to 100ms. (This is the main WIP part)

It could be argued that creating a thread for progress reporting adds
overhead, but I would counter that by saying "If the task is so fast
that creating a thread noticably slows it down, then it really doesn't
need progress reporting".

For me, this speeds up DWARF indexing by about 1.5% (which is only
slightly above the error bars), but I expect it will have a much bigger
impact in situations where printing a single progress update takes a
nontrivial amount of time.

Diff Detail

Event Timeline

labath created this revision.Jun 7 2023, 4:43 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 7 2023, 4:43 AM
labath requested review of this revision.Jun 7 2023, 4:43 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 7 2023, 4:43 AM

Continuing some of the discussion from D150805 here as it mostly relates to this patch:

I agree that we should have a rate limiting mechanism very close to the source, to avoid wasting work for events that aren't going to be used.

I'm not sure I (fully) agree with this statement. I may have misunderstood the motivation for D150805, but my understanding was that it was the consumption of the progress events that was too costly, not the generation. Of course this will indirectly make that problem better because of the rate limiting, but as I said in the original review, that still seems like something the consumer should have control over.

I would have assumed that reporting the progress wouldn't be that expensive. Based on the description of this patch, it seems like it's non-trivial, but also not a dominating factor by any means.

This is particularly important for debug info parsing, where we have multiple threads working in parallel and taking a lock even just to check whether we should report something could be a choke point.

Fair enough, but this could be achieved without the rate limiting: the reporting could be an async operation with the thread reporting every event.

I like it because the act of reporting progress does block the reporting thread in any way.

+1 but as I said above this could be orthogonal from rate limiting.

TL;DR I like the async reporting part, not (yet) convinced of the rate limiting "at the source".

What other progress reporting needs rate limiting?

To the best of my knowledge, we have only identified one location--this one. So I'm not sure a fully general solution is in order here under the YAGNI principle.

I favor rate limiting close to the source because generating events and throwing them away is pure waste and gives an overall sense to the user that lldb is slow.

What other progress reporting needs rate limiting?

To the best of my knowledge, we have only identified one location--this one. So I'm not sure a fully general solution is in order here under the YAGNI principle.

I favor rate limiting close to the source because generating events and throwing them away is pure waste and gives an overall sense to the user that lldb is slow.

While generality is part of why I favor doing the rate limiting in the listener, it also means that the listener can decide the rate. For example, VSCode could decide they don't need rate limiting (as is the case today) while the default event handler in LLDB could make a different decision (for example based on whether you're in a fast TTY).

labath added a comment.Jun 9 2023, 1:00 AM

Continuing some of the discussion from D150805 here as it mostly relates to this patch:

I agree that we should have a rate limiting mechanism very close to the source, to avoid wasting work for events that aren't going to be used.

I'm not sure I (fully) agree with this statement. I may have misunderstood the motivation for D150805, but my understanding was that it was the consumption of the progress events that was too costly, not the generation. Of course this will indirectly make that problem better because of the rate limiting, but as I said in the original review, that still seems like something the consumer should have control over.

I would have assumed that reporting the progress wouldn't be that expensive. Based on the description of this patch, it seems like it's non-trivial, but also not a dominating factor by any means.

That is correct. It is definitely not as slow as I expected it to be at first.

This is particularly important for debug info parsing, where we have multiple threads working in parallel and taking a lock even just to check whether we should report something could be a choke point.

Fair enough, but this could be achieved without the rate limiting: the reporting could be an async operation with the thread reporting every event.

That's a bit tricky. If you want to guarantee you don't lose any events *and* also not block the "sending" thread , you have to have some sort of a queuing mechanism -- which means you're essentially reimplementing a listener/broadcaster pattern. I'm sure that could be implemented more efficiently that what our current classes do, but I don't think that would be time well spent. And we'd still be generating a lot of events that noone is going to see.

While generality is part of why I favor doing the rate limiting in the listener, it also means that the listener can decide the rate. For example, VSCode could decide they don't need rate limiting (as is the case today) while the default event handler in LLDB could make a different decision (for example based on whether you're in a fast TTY).

The idea seems nice in principle, but I think the implementation would be somewhat messy. For "normal" events, the goal usually is to send them out as quickly as possible, but in this case we actually want to do the opposite -- force a delay before the receipt (or sending) of a specific event. And as the same listener will be receiving multiple kinds of events, be need this to be configurable on a per-event basis. If I was going down this path, I guess I'd do it by adding some kind of a delay/frequency argument to Listener::StartListeningForEvents function. The listener would remember the requested frequency for a specific type of events as well as the last time that this kind of an event was received, and then the broadcaster would avoid enqueuing these events (and waking up the listener) if the events come faster than that.

Doable, just slightly complicated. We'd also need to figure out what we do with the existing filtering mechanism -- looking at the code, I see that we already have the ability to send "unique" events (Broadcaster::BroadcastEventIfUnique). This is a form of rate-limiting, so I think it'd make sense to merge these. However:

  • this behavior is controlled on the broadcaster side
  • this actually keeps the first duplicated event, whereas we'd most likely prefer to keep the last one
labath added a comment.Jun 9 2023, 1:06 AM

Actually, thinking about these "unique" events, I think it would be worth trying out sending the progress update events as "unique". Depending on where exactly in the pipeline the congestion happens, it might just be enough to fix the immediate problem. If the slow screen updates cause the write(stdout) calls to block, then we will have a queue forming in the listener object. The uniqueness property would help with that as it would collapse that queue into a single event. If the write does not block (and the queue forms somewhere down the line), then we need rate limiting.

This revision was not accepted when it landed; it landed in state Needs Review.Jun 16 2023, 12:09 AM
This revision was automatically updated to reflect the committed changes.