Skip to content
This repository has been archived by the owner on Nov 14, 2024. It is now read-only.

Better coalescing supplier #4508

Merged
merged 17 commits into from
Jan 14, 2020
Merged

Conversation

j-baker
Copy link
Contributor

@j-baker j-baker commented Jan 13, 2020

This is a more readable version of the present coalescing supplier. Won't describe present state of world (algorithm is confusing) but the new implementation is easier to understand, and in some cases demonstrates better performance.

For each round, either we are the first to arrive (execute and return)
or we are not.

In the case we are not, we await the current round ending and then
perform the check again. If not this time, we wait for the executor to
finish and return their result.

The improvement is hard to measure in synthetic benchmarks. With spinning threads it's actually slower than the present day (due to the fair queue, the present code maximizes the likelihood that the batch will be full, whereas the new code will likely ensure that the completing task starts the next batch.

With some random noise delays added to smooth out this effect, performance is very similar to at present, possibly slightly less variance. It's possible to tweak any benchmark by adding extraneous loading.

  1. The delays we're seeing are unpredictable - the sorts of things that would be the fault of the OS scheduler.
  2. This solution does not show the bad behaviour that we think we're seeing of a thread waking another thread up and that thread not being scheduled for a while.

This is a moderate improvement on the present coalescing supplier.
Won't describe present state of the world, but the new implementation
hopefully describes the algorithm better.

For each round, either we are the first to arrive (execute and return)
or we are not.

In the case we are not, we await the current round ending and then
perform the check again. If not this time, we wait for the executor to
finish and return their result.

The improvement is modest - with 16 threads looping and a task that takes
2ms (the benchmark) we see throughput of 6886 +- 73 operations per
second. With this change, we see a result of 7232 +- 89 operations per
second.

While the change is minimal, the result is closer to optimal;
16 / 0.002 = 8000 as perfect (which we can never really achieve in such
a benchmark).
@changelog-app
Copy link

changelog-app bot commented Jan 13, 2020

Generate changelog in changelog/@unreleased

Type

  • Feature
  • Improvement
  • Fix
  • Break
  • Deprecation
  • Manual task
  • Migration

Description

More predictable coalescing supplier

Check the box to generate changelog(s)

  • Generate changelog entry

private final class Round {
private final AtomicBoolean hasStarted = new AtomicBoolean(false);
private final CompletableFuture<T> future = new CompletableFuture<>();
private volatile Round next;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's unclear to me based on Java semantics whether this needs to be volatile or not. But I'm gonna leave it as such.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think not, since completing a future does happen before a join on that future. Not 100% confident in this one so agree with leaving it as such.

private volatile Round next;

boolean isFirstToArrive() {
return !hasStarted.get() && hasStarted.compareAndSet(false, true);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much faster than the equivalent 'hasStarted.compareAndSet(false, true)'. I believe this is because the proposed solution can do the read in a MESI shared state, whereas the CAS will always make it exclusive.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I think we should document this in a comment here.

try {
future.join();
} catch (CompletionException e) {
// ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we eating this and not at least logging at lower level?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's intended behaviour - we're literally just awaiting the conclusion of the future

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we just want it to be complete. We shouldn't do anything with the exception because it's never relevant in this method.

@j-baker j-baker changed the title Faster coalescing supplier Better coalescing supplier Jan 13, 2020
Copy link
Contributor

@jeremyk-91 jeremyk-91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally makes sense. I think the build is currently failing as this needs to be rebased on top of #4505

private final class Round {
private final AtomicBoolean hasStarted = new AtomicBoolean(false);
private final CompletableFuture<T> future = new CompletableFuture<>();
private volatile Round next;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think not, since completing a future does happen before a join on that future. Not 100% confident in this one so agree with leaving it as such.

private volatile Round next;

boolean isFirstToArrive() {
return !hasStarted.get() && hasStarted.compareAndSet(false, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I think we should document this in a comment here.

Copy link
Contributor

@jeremyk-91 jeremyk-91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good.

@jeremyk-91 jeremyk-91 merged commit 0708514 into develop Jan 14, 2020
@delete-merged-branch delete-merged-branch bot deleted the jbaker/faster_coalescing_supplier branch January 14, 2020 18:48
@svc-autorelease
Copy link
Collaborator

Released 0.179.1

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants