-
-
Notifications
You must be signed in to change notification settings - Fork 15.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow to customize NIO (channel) select strategies. #4955
Conversation
4e18e9e
to
e8fa1b8
Compare
@buchgr a change similar to this would maybe fit your needs better? no need for a separate event loop and pluggable backoff mechanisms. |
* @return {@link NoopBackoffStrategy} as the default. | ||
*/ | ||
public BackoffStrategy backoffStrategy() { | ||
return new NoopBackoffStrategy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make NoopBackoffStrategy a singleton?
Also if this is baked into NioEventLoop
anyways should we just add another constructor to NioEventLoopGroup
and accept a BackoffStrategy
argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Scottmitch I considered that too, the only problem I had with this is that we need to call it N times (once per loop) so one strategy isn't enough - we'd need some kind of factory and then I thought well this might be simpler.
I had the singleton for the Noop there already, I can add it back for sure :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we'd need some kind of factory
Yip makes sense if the BackoffStrategy
has state. However this approach seems more portable if we eventually want to consider this patch for EpollEventLoop too.
@NiteshKant I see, that makes sense. I kinda like the I also incorporated the singleton and factory as @Scottmitch suggested. I'm keeping in the PR for now so we can talk about potential extensions :) |
@normanmaurer new day new update ;) |
@daschl yes the |
* Default {@link BackoffStrategy} which does not perform backoff and will immediately trigger | ||
* the selector blocking. | ||
*/ | ||
public class NoopBackoffStrategy implements BackoffStrategy { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this class final and add a private constructor.
@normanmaurer @trustin - Any thoughts about this? @daschl - Can you also add a JMH benchmark demonstrating the benefits and ensuring this change doesn't regress for the "in event loop" use case? |
@Scottmitch will rework it, but not sure about JMH (since there are many different workloads in theory that could be affected. I'll see if I can come up with something sane) |
updated |
} else { | ||
boolean hasTasks = hasTasks(); | ||
int selectNowCount = selectNow(); | ||
if (backoff.delaySelect(selectNowCount, hasTasks)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it a bit more and I think we can change the interface to replicate the existing behavior and also allow for extension. This strategy allows us to avoid an extra system call on every loop iteration in the default case and will also work with EpollEventLoop. By being able to retain the existing behavior it is less risky and much less you have to prove in terms of performance impact.
// This can go somewhere in common and is equivalent to Supplier from java 8,
// but with a native type and we can't use Supplier from java 8 bcz
// we must support java 6.
public interface IntSupplier {
int get();
}
// BackofStrategy changes to SelectStrategy
public interface SelectStrategy {
// return value: -2 means try again, -1 means select, anything else is the result of selectNowSupplier.selectNow() and will run all tasks
int calculateStrategy(IntSupplier selectNowSupplier, boolean hasTasks);
}
public final class DefaultSelectStrategy implements SelectStrategy {
// this class should be a singleton.
public int calculateStrategy(IntSupplier supplier, boolean hasTasks) {
return hasTasks ? supplier.get() : -1;
}
}
// In NioEventLoop:
private final IntSupplier SELECT_NOW_SUPPLIER = new IntSupplier() {
public int get() {
return selectNow();
}
};
..
selectloop: for(;;) {
try {
switch(selectStrategy.calculateStrategy(SELECT_NOW_SUPPLIER, hasTasks()) {
case -2:
continue selectloop;
case -1:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
cancelledKeys = 0;
needsToSelectAgain = false;
...
break;
}
9d00065
to
4bb27e9
Compare
@Scottmitch updated the PR with your suggestions, let me know if that is what you are looking for :) The only thing I'm not sure about is how we should handle a IOException in the supplier, right now I log the error, return 0 so that the code moves on (since there is not much at this point we can do, right?). If that one looks good I'll get rid of the parking impl and polish it up so that we can get it ready for final review/merging? |
@normanmaurer good time for you to review too I think. |
*/ | ||
public final class DefaultSelectStrategy implements SelectStrategy { | ||
|
||
public static final DefaultSelectStrategy INSTANCE = new DefaultSelectStrategy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/DefaultSelectStrategy/SelectStrategy/
I gave the change a try. Similar results for lettuce. A continuous loop using full request/response cycles on one thread results in a 5.5k ops/s increase (before the change ~ 16k ops/sec, after ~ 21.5k ops/sec)
performed best (some 500 to 1000 ops/sec faster than the @daschl strategy). |
Nice job! LGTM |
@normanmaurer - Any objections? |
Will check today and if not pull it in
|
case SelectStrategy.CONTINUE: | ||
continue selectloop; | ||
case SelectStrategy.SELECT: | ||
select(wakenUp.getAndSet(false)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daschl should we do the same as before here:
- if (hasTasks()) {
- selectNow();
- } else {
- select(oldWakenUp);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
technically that's what we have, right? and its up to the strategy to call selectNow(). Or am I misunderstanding you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@normanmaurer - As @daschl said the intention is to replicate the existing behavior in DefaultSelectStrategy
... it seems this way to me but do you see a bug in the current code?
@normanmaurer why did this not make it into 4.0.35? :( :( |
@daschl will release 4.0.36.Final next week and include it. Just want to run some more benchmarks first |
@normanmaurer sounds good to me, thanks! (rebased) |
Motivation: Under high throughput/low latency workloads, selector wakeups are degrading performance when the incoming operations are triggered from outside of the event loop. This is a common scenario for "client" applications where the originating input is coming from application threads rather from the socket attached inside the event loops. As a result, it can be desirable to defer the blocking select so that incoming tasks (write/flush) do not need to wakeup the selector. Modifications: This changeset adds the notion of a generic SelectStrategy which, based on its contract, allows the implementation to optionally defer the blocking select based on some custom criteria. The default implementation resembles the original behaviour, that is if tasks are in the queue `selectNow()` and move on, and if no tasks need to be processed go into the blocking select and wait for wakeup. The strategy can be customized per `NioEventLoopGroup` in the constructor. Result: High performance client applications are now given the chance to customize for how long the actual selector blocking should be deferred by employing a custom select strategy.
@Scottmitch @daschl yeah LGTM. I just want to do some benchmarking before pulling this in next week. |
@normanmaurer - Ok SGTM. I'll pull together a PR for EPOLL too so we can keep them consistent in the next release. |
@Scottmitch SGTM... thanks! |
@normanmaurer - May be easier to benchmark with #5044 since it includes this PR and EPOLL updates. |
So I did some tests and this looks good... @nmittler anything you want to add here or you are satisfied as well ? |
/** | ||
* Represents a supplier of {@code int}-valued results. | ||
*/ | ||
public interface IntSupplier { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use javax.inject.Provider? Are you worried about performance of autoboxing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mostly because I didn't know this existed and in java8 there is a java.util.function.Supplier which is not is not tied to injectors
through convention or javadocs. I think I still prefer the IntSupplier
if we can't use java8 classes...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nmittler I agree with @Scottmitch here :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm
LGTM |
🎉 |
This PR is still a work in progress, subject to be reviewed by @normanmaurer (and others who are interested, of course). Supersedes #4952