Added option to do busy-wait on epoll (#8267)

Motivation:

Add an option (through a SelectStrategy return code) to have the Netty event loop thread to do busy-wait on the epoll.

The reason for this change is to avoid the context switch cost that comes when the event loop thread is blocked on the epoll_wait() call.

On average, the context switch has a penalty of ~13usec.

This benefits both:

The latency when reading from a socket
Scheduling tasks to be executed on the event loop thread.
The tradeoff, when enabling this feature, is that the event loop thread will be using 100% cpu, even when inactive.

Modification:

Added SelectStrategy option to return BUSY_WAIT
Epoll loop will do a epoll_wait() with no timeout
Use pause instruction to hint to processor that we're in a busy loop
Result:

When enabled, minimizes impact of context switch in the critical path
This commit is contained in:
Matteo Merli 2018-09-28 13:52:00 -07:00 committed by Norman Maurer
parent a95b7a791e
commit 3a96e7373b
9 changed files with 227 additions and 0 deletions

View File

@ -213,6 +213,33 @@ static jint netty_epoll_native_epollWait0(JNIEnv* env, jclass clazz, jint efd, j
return -err;
}
static inline void cpu_relax() {
#if defined(__x86_64__)
asm volatile("pause\n": : :"memory");
#endif
}
static jint netty_epoll_native_epollBusyWait0(JNIEnv* env, jclass clazz, jint efd, jlong address, jint len) {
struct epoll_event *ev = (struct epoll_event*) (intptr_t) address;
int result, err;
// Zeros = poll (aka return immediately).
do {
result = epoll_wait(efd, ev, len, 0);
if (result == 0) {
// Since we're always polling epoll_wait with no timeout,
// signal CPU that we're in a busy loop
cpu_relax();
}
if (result >= 0) {
return result;
}
} while((err = errno) == EINTR);
return -err;
}
static jint netty_epoll_native_epollCtlAdd0(JNIEnv* env, jclass clazz, jint efd, jint fd, jint flags) {
int res = epollCtl(env, efd, EPOLL_CTL_ADD, fd, flags);
if (res < 0) {
@ -387,6 +414,7 @@ static const JNINativeMethod fixed_method_table[] = {
{ "timerFdRead", "(I)V", (void *) netty_epoll_native_timerFdRead },
{ "epollCreate", "()I", (void *) netty_epoll_native_epollCreate },
{ "epollWait0", "(IJIIII)I", (void *) netty_epoll_native_epollWait0 },
{ "epollBusyWait0", "(IJI)I", (void *) netty_epoll_native_epollBusyWait0 },
{ "epollCtlAdd0", "(III)I", (void *) netty_epoll_native_epollCtlAdd0 },
{ "epollCtlMod0", "(III)I", (void *) netty_epoll_native_epollCtlMod0 },
{ "epollCtlDel0", "(II)I", (void *) netty_epoll_native_epollCtlDel0 },

View File

@ -257,6 +257,10 @@ final class EpollEventLoop extends SingleThreadEventLoop {
return Native.epollWait(epollFd, events, timerFd, 0, 0);
}
private int epollBusyWait() throws IOException {
return Native.epollBusyWait(epollFd, events);
}
@Override
protected void run() {
for (;;) {
@ -265,6 +269,11 @@ final class EpollEventLoop extends SingleThreadEventLoop {
switch (strategy) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
strategy = epollBusyWait();
break;
case SelectStrategy.SELECT:
strategy = epollWait(WAKEN_UP_UPDATER.getAndSet(this, 0) == 1);

View File

@ -120,6 +120,21 @@ public final class Native {
}
private static native int epollWait0(int efd, long address, int len, int timerFd, int timeoutSec, int timeoutNs);
/**
* Non-blocking variant of
* {@link #epollWait(FileDescriptor, EpollEventArray, FileDescriptor, int, int)}
* that will also hint to processor we are in a busy-wait loop.
*/
public static int epollBusyWait(FileDescriptor epollFd, EpollEventArray events) throws IOException {
int ready = epollBusyWait0(epollFd.intValue(), events.memoryAddress(), events.length());
if (ready < 0) {
throw newIOException("epoll_wait", ready);
}
return ready;
}
private static native int epollBusyWait0(int efd, long address, int len);
public static void epollCtlAdd(int efd, final int fd, final int flags) throws IOException {
int res = epollCtlAdd0(efd, fd, flags);
if (res < 0) {

View File

@ -0,0 +1,31 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.epoll;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBufAllocator;
public class EpollETSocketStringEchoBusyWaitTest extends EpollSocketStringEchoBusyWaitTest {
@Override
protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) {
super.configure(bootstrap, bootstrap2, allocator);
bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED)
.childOption(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED);
bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED);
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.epoll;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBufAllocator;
public class EpollLTSocketStringEchoBusyWaitTest extends EpollSocketStringEchoBusyWaitTest {
@Override
protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) {
super.configure(bootstrap, bootstrap2, allocator);
bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED)
.childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED);
bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED);
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.channel.epoll;
import java.util.ArrayList;
import java.util.List;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SelectStrategy;
import io.netty.channel.SelectStrategyFactory;
import io.netty.testsuite.transport.TestsuitePermutation;
import io.netty.testsuite.transport.TestsuitePermutation.BootstrapComboFactory;
import io.netty.testsuite.transport.TestsuitePermutation.BootstrapFactory;
import io.netty.testsuite.transport.socket.SocketStringEchoTest;
import io.netty.util.IntSupplier;
import io.netty.util.concurrent.DefaultThreadFactory;
public class EpollSocketStringEchoBusyWaitTest extends SocketStringEchoTest {
private static EventLoopGroup EPOLL_LOOP;
@BeforeClass
public static void setup() throws Exception {
EPOLL_LOOP = new EpollEventLoopGroup(2, new DefaultThreadFactory("testsuite-epoll-busy-wait", true),
new SelectStrategyFactory() {
@Override
public SelectStrategy newSelectStrategy() {
return new SelectStrategy() {
@Override
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) {
return SelectStrategy.BUSY_WAIT;
}
};
}
});
}
@AfterClass
public static void teardown() throws Exception {
if (EPOLL_LOOP != null) {
EPOLL_LOOP.shutdownGracefully();
}
}
@Override
protected List<TestsuitePermutation.BootstrapComboFactory<ServerBootstrap, Bootstrap>> newFactories() {
List<BootstrapComboFactory<ServerBootstrap, Bootstrap>> list =
new ArrayList<BootstrapComboFactory<ServerBootstrap, Bootstrap>>();
final BootstrapFactory<ServerBootstrap> sbf = serverSocket();
final BootstrapFactory<Bootstrap> cbf = clientSocket();
list.add(new BootstrapComboFactory<ServerBootstrap, Bootstrap>() {
@Override
public ServerBootstrap newServerInstance() {
return sbf.newInstance();
}
@Override
public Bootstrap newClientInstance() {
return cbf.newInstance();
}
});
return list;
}
private static BootstrapFactory<ServerBootstrap> serverSocket() {
return new BootstrapFactory<ServerBootstrap>() {
@Override
public ServerBootstrap newInstance() {
return new ServerBootstrap().group(EPOLL_LOOP, EPOLL_LOOP).channel(EpollServerSocketChannel.class);
}
};
}
private static BootstrapFactory<Bootstrap> clientSocket() {
return new BootstrapFactory<Bootstrap>() {
@Override
public Bootstrap newInstance() {
return new Bootstrap().group(EPOLL_LOOP).channel(EpollSocketChannel.class);
}
};
}
}

View File

@ -208,6 +208,10 @@ final class KQueueEventLoop extends SingleThreadEventLoop {
switch (strategy) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
// fall-through to SELECT since the busy-wait is not supported with kqueue
case SelectStrategy.SELECT:
strategy = kqueueWait(WAKEN_UP_UPDATER.getAndSet(this, 0) == 1);

View File

@ -33,6 +33,10 @@ public interface SelectStrategy {
* Indicates the IO loop should be retried, no blocking select to follow directly.
*/
int CONTINUE = -2;
/**
* Indicates the IO loop to poll for new events without blocking.
*/
int BUSY_WAIT = -3;
/**
* The {@link SelectStrategy} can be used to steer the outcome of a potential select

View File

@ -404,6 +404,10 @@ public final class NioEventLoop extends SingleThreadEventLoop {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
// fall-through to SELECT since the busy-wait is not supported with NIO
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));