Skip to content
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

Closed
wants to merge 1 commit into from

Conversation

daschl
Copy link
Member

@daschl daschl commented Mar 9, 2016

This PR is still a work in progress, subject to be reviewed by @normanmaurer (and others who are interested, of course). Supersedes #4952

@daschl daschl force-pushed the backoff branch 2 times, most recently from 4e18e9e to e8fa1b8 Compare March 9, 2016 15:42
@daschl
Copy link
Member Author

daschl commented Mar 9, 2016

@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();
Copy link
Member

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?

Copy link
Member Author

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 :)

Copy link
Member

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.

@daschl
Copy link
Member Author

daschl commented Mar 10, 2016

@NiteshKant I see, that makes sense. I kinda like the reset approach, what do you think about the last change?

I also incorporated the singleton and factory as @Scottmitch suggested.
I'm still thinking if we get this in we should not include the parking strategy since its very likely that the hardcoded parts are not good for every use case and to make it more tunable we need more experience on what to actually tune - unless someone comes up with a sane strategy and defaults.

I'm keeping in the PR for now so we can talk about potential extensions :)

@daschl
Copy link
Member Author

daschl commented Mar 10, 2016

@normanmaurer new day new update ;)

@NiteshKant
Copy link
Member

@daschl yes the reset() in the strategy looks good 👍

* Default {@link BackoffStrategy} which does not perform backoff and will immediately trigger
* the selector blocking.
*/
public class NoopBackoffStrategy implements BackoffStrategy {
Copy link
Member

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.

@Scottmitch Scottmitch self-assigned this Mar 11, 2016
@Scottmitch
Copy link
Member

@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?

@daschl
Copy link
Member Author

daschl commented Mar 11, 2016

@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)

@daschl
Copy link
Member Author

daschl commented Mar 11, 2016

updated

} else {
boolean hasTasks = hasTasks();
int selectNowCount = selectNow();
if (backoff.delaySelect(selectNowCount, hasTasks)) {
Copy link
Member

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;
    }

@daschl daschl force-pushed the backoff branch 2 times, most recently from 9d00065 to 4bb27e9 Compare March 14, 2016 13:02
@daschl
Copy link
Member Author

daschl commented Mar 14, 2016

@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?

@daschl
Copy link
Member Author

daschl commented Mar 14, 2016

@normanmaurer good time for you to review too I think.

*/
public final class DefaultSelectStrategy implements SelectStrategy {

public static final DefaultSelectStrategy INSTANCE = new DefaultSelectStrategy();
Copy link
Member

Choose a reason for hiding this comment

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

s/DefaultSelectStrategy/SelectStrategy/

@mp911de
Copy link

mp911de commented Mar 17, 2016

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)
In my tests

    @Override
    public int calculateStrategy(final IntSupplier supplier, final boolean hasTasks)
        throws Exception {
        return supplier.get();
    }

performed best (some 500 to 1000 ops/sec faster than the @daschl strategy).

@Scottmitch
Copy link
Member

@daschl - No problem. I'll pickup the EPOLL portion.

@mp911de - Thanks for sharing!

@trustin
Copy link
Member

trustin commented Mar 23, 2016

Nice job! LGTM

@Scottmitch
Copy link
Member

@normanmaurer - Any objections?

@normanmaurer
Copy link
Member

Will check today and if not pull it in

Am 24.03.2016 um 05:44 schrieb Scott Mitchell notifications@github.com:

@normanmaurer - Any objections?


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub

case SelectStrategy.CONTINUE:
continue selectloop;
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
Copy link
Member

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

Copy link
Member Author

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?

Copy link
Member

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?

@daschl
Copy link
Member Author

daschl commented Mar 25, 2016

@normanmaurer why did this not make it into 4.0.35? :( :(

@normanmaurer
Copy link
Member

@daschl will release 4.0.36.Final next week and include it. Just want to run some more benchmarks first

@daschl
Copy link
Member Author

daschl commented Mar 25, 2016

@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.
@normanmaurer
Copy link
Member

@Scottmitch @daschl yeah LGTM. I just want to do some benchmarking before pulling this in next week.

@Scottmitch
Copy link
Member

@normanmaurer - Ok SGTM. I'll pull together a PR for EPOLL too so we can keep them consistent in the next release.

@normanmaurer
Copy link
Member

@Scottmitch SGTM... thanks!

@Scottmitch Scottmitch mentioned this pull request Mar 25, 2016
@Scottmitch
Copy link
Member

@normanmaurer - May be easier to benchmark with #5044 since it includes this PR and EPOLL updates.

@normanmaurer
Copy link
Member

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 {
Copy link
Member

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?

Copy link
Member

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...

Copy link
Member

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 :)

Copy link
Member

Choose a reason for hiding this comment

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

sgtm

@nmittler
Copy link
Member

LGTM

@Scottmitch Scottmitch assigned Scottmitch and unassigned daschl Mar 30, 2016
@Scottmitch
Copy link
Member

Cherry-picked 4.1 (5d76daf) 4.0 (24cb673)

Thanks @daschl !

@Scottmitch Scottmitch closed this Mar 30, 2016
@daschl
Copy link
Member Author

daschl commented Mar 31, 2016

🎉

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

Successfully merging this pull request may close these issues.

None yet

9 participants