SocketChannel:为什么我快速写msgs每个消息的延迟很低,但是当我每30秒写一个msg时,延迟很高?

时间:2017-04-12 18:59:56

标签: java sockets networking jvm real-time

现在在这个新问题中明确地描述了这个问题的发展:Why does the JVM show more latency for the same block of code after a busy spin pause?

我在下面列出了一个简单的服务器和客户端的源代码,用于演示和隔离问题。基本上我是计时乒乓(客户端 - 服务器 - 客户端)消息的延迟时间。我首先每1毫秒发送一条消息。我等待发送200k消息,以便HotSpot有机会优化代码。然后我将暂停时间从1毫秒更改为30秒。令我惊讶的是,我的写入和读取操作变得相当慢。

我不认为这是JIT / HotSpot问题。我能够找到原始JNI调用写入(write0)和读取的较慢方法。看起来你停的时间越长越慢。

我正在寻找有关如何调试,理解,解释或解决此问题的指示。

Server.java:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class Server {

    private final ServerSocketChannel serverSocketChannel;
    private final ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);
    private final int port;
    private final int msgSize;

    public Server(int port, int msgSize) throws IOException {
        this.serverSocketChannel = ServerSocketChannel.open();
        this.port = port;
        this.msgSize = msgSize;
    }

    public void start() throws IOException {
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        final SocketChannel socketChannel = serverSocketChannel.accept(); // blocking mode...
        System.out.println("Client accepted!");
        socketChannel.configureBlocking(false);
        socketChannel.socket().setTcpNoDelay(true);
        Thread t = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    while(true) {
                        int bytesRead = socketChannel.read(readBuffer);
                        if (bytesRead == -1) {
                            System.out.println("Client disconnected!");
                            return;
                        } else if (bytesRead > 0) {
                            if (readBuffer.position() == msgSize) {
                                // have a full message there...
                                readBuffer.flip();
                                int bytesSent = socketChannel.write(readBuffer);
                                if (bytesSent != msgSize) throw new RuntimeException("Could not send full message out: " + bytesSent);
                                readBuffer.clear();
                            }
                        }
                    }
                } catch(Exception e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        serverSocketChannel.close();
    }

    public static void main(String[] args) throws Exception {

        Server s = new Server(9999, 8);
        s.start();
    }
}

Client.java:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Client implements Runnable {

    private static final int WARMUP = 200000;

    private final SocketChannel socketChannel;
    private final String host;
    private final int port;
    private final ByteBuffer outBuffer;
    private final ByteBuffer inBuffer = ByteBuffer.allocateDirect(1024);
    private final int msgSize;
    private final StringBuilder sb = new StringBuilder(1024);

    private int interval;
    private int totalMessagesSent;
    private long timeSent;
    private int mod;


    public Client(String host, int port, int msgSize) throws IOException {
        this.socketChannel = SocketChannel.open();
        this.host = host;
        this.port = port;
        this.outBuffer = ByteBuffer.allocateDirect(msgSize);
        this.msgSize = msgSize;
        for(int i = 0; i < msgSize; i++) outBuffer.put((byte) i);
        outBuffer.flip();
        this.interval = 1;
        this.mod = 20000;
    }

    public static long busySleep(long t) {
        long x = 0;
        for(int i = 0; i < t * 20000; i++) {
            x += System.currentTimeMillis() / System.nanoTime();
        }
        return x;
    }

    public void start() throws Exception {
        this.socketChannel.configureBlocking(false);
        this.socketChannel.socket().setTcpNoDelay(true);
        this.socketChannel.connect(new InetSocketAddress(host, port));

        while(!socketChannel.finishConnect()) {
            System.out.println("Waiting to connect");
            Thread.sleep(1000);
        }
        System.out.println("Please wait as output will appear every minute or so. After " + WARMUP + " messages you will see the problem.");
        Thread t = new Thread(this);
        t.start();
    }

    private final void printResults(long latency, long timeToWrite, long timeToRead, long zeroReads, long partialReads, long realRead) {
        sb.setLength(0);
        sb.append(new java.util.Date().toString());
        sb.append(" Results: totalMessagesSent=").append(totalMessagesSent);
        sb.append(" currInterval=").append(interval);
        sb.append(" latency=").append(latency);
        sb.append(" timeToWrite=").append(timeToWrite);
        sb.append(" timeToRead=").append(timeToRead);
        sb.append(" realRead=").append(realRead);
        sb.append(" zeroReads=").append(zeroReads);
        sb.append(" partialReads=").append(partialReads);
        System.out.println(sb);
    }

    @Override
    public void run() {

        try {

            while(true) {

                busySleep(interval);

                outBuffer.position(0);

                timeSent = System.nanoTime();

                int bytesSent = socketChannel.write(outBuffer);
                long timeToWrite = System.nanoTime() - timeSent;
                if (bytesSent != msgSize) throw new IOException("Can't write message: " + bytesSent);

                inBuffer.clear();
                long zeroReads = 0;
                long partialReads = 0;
                long timeToRead = System.nanoTime();
                long realRead = 0;
                while(inBuffer.position() != msgSize) {
                    realRead = System.nanoTime();
                    int bytesRead = socketChannel.read(inBuffer);
                    if (bytesRead == 0) {
                        zeroReads++;
                    } else if (bytesRead == -1) {
                        System.out.println("Other side disconnected!");
                        return;
                    } else if (bytesRead != msgSize) {
                        partialReads++;
                        realRead = -1;
                    } else {
                        realRead = System.nanoTime() - realRead;
                    }
                }

                long now = System.nanoTime();

                timeToRead = now - timeToRead;

                long latency = now - timeSent;

                if (++totalMessagesSent % mod == 0 || totalMessagesSent == 1) {
                    printResults(latency, timeToWrite, timeToRead, zeroReads, partialReads, realRead);
                }

                if (totalMessagesSent == WARMUP) {
                    this.interval = 30000;
                    this.mod = 1;
                }
            }

        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {

        Client client = new Client("localhost", 9999, 8);
        client.start();
    }
}

我执行java -server -cp . Serverjava -server -cp . Client。客户端的输出是: enter image description here

每@dunni请求,更改为1秒延迟而不是30秒延迟。同样的问题: enter image description here

2 个答案:

答案 0 :(得分:1)

您遇到的一个问题是,当没有数据要读取时,JVM,CPU及其缓存正在入睡。一旦发生这种情况,机器在获取数据之前必须做的事情远远超过问题运行时的情况。

  • CPU速度可能已下降以节省电量。例如半正常。它可以在愚蠢的繁忙循环中执行此操作。
  • 线程未运行,必须在新CPU上重新启动。 (在你的情况下,这应该是罕见的)
  • CPU的缓存可能已关闭,必须从L3缓存或主内存逐步加载
  • 即使在你的线程返回后,它也会比平常运行速度慢100个微秒,因为缓存会提取更多的数据/代码。
  • 你将无法获得每秒100次的不可屏蔽中断,你无法关闭。

简而言之,如果您需要一致的延迟,则需要

  • 关闭电源管理。
  • 不要放弃CPU,即忙等待。 (你在做什么)
  • 在隔离的CPU上运行,使用亲和力绑定线程。
  • 禁用该核心上的所有可屏蔽中断。
  • 使用用户空间驱动程序代替内核进行联网。

注意:鉴于每个操作似乎需要大约2倍的时间,我会首先考虑电源管理。

答案 1 :(得分:1)

我在查看SocketChannelImpl的代码 并注意到read()中涉及两个监视器 - 读锁定和状态锁定。

我的观点是,当它们很热且无争议时,锁的表现会好得多。

以下类基于您的客户端,只进行一些锁定,类似于SocketChannelImpl中的操作。从不可观察的角度来看,我的盒子上的延迟变为~5000(win8,jdk8)

import java.util.concurrent.TimeUnit;

public class Locker implements Runnable {

private static final int WARMUP = 40000;

private final Object readLock = new Object();
private final Object writeLock = new Object();
private final Object stateLock = new Object();

private final StringBuilder sb = new StringBuilder(1024);

private long interval;
private int totalMessagesSent;
private long timeSent;
private int mod;
private long totalOps;
private long readerThread;
private long writerThread;


public Locker() {
    this.interval = 1;
    this.mod = 20000;
}

public static long busySleep(long t) throws InterruptedException {
    long until = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(t);
    while(System.nanoTime() < until);
    return until;
}

public void start() throws Exception {
    Thread t = new Thread(this);
    t.start();
}

private final void printResults(long latency, long timeToRead) {
    sb.setLength(0);
    sb.append(new java.util.Date().toString());
    sb.append(" Results: totalMessagesSent=").append(totalMessagesSent);
    sb.append(" currInterval=").append(interval);
    sb.append(" latency=").append(latency);
    sb.append(" timeToRead=").append(timeToRead);
    sb.append(" totalOps=").append(totalOps);
    sb.append(" reader=").append(readerThread);
    sb.append(" writer=").append(writerThread);
    System.out.println(sb);
}

@Override
public void run() {

    try {
        while(true) {

            busySleep(interval);

            timeSent = System.nanoTime();

            try {
                synchronized (writeLock) {
                    synchronized (stateLock) {
                        writerThread = Thread.currentThread().getId();
                    }
                    totalOps++;
                }
            }
            finally {
                synchronized (stateLock) {
                    writerThread = 0;
                }
            }

            long timeToRead = System.nanoTime();

            try {
                synchronized (readLock) {
                    synchronized (stateLock) {
                        readerThread = Thread.currentThread().getId();
                    }
                    totalOps++;
                }
            } finally {
                synchronized (stateLock) {
                    readerThread = 0;
                }
            }

            long now = System.nanoTime();

            timeToRead = now - timeToRead;

            long latency = now - timeSent;

            if (++totalMessagesSent % mod == 0 || totalMessagesSent == 1) {
                printResults(latency, timeToRead);
            }

            if (totalMessagesSent == WARMUP) {
                this.interval = 5000;
                this.mod = 1;
            }
        }

    } catch(Exception e) {
        throw new RuntimeException(e);
    }
}

public static void main(String[] args) throws Exception {
    Locker locker = new Locker();
    locker.start();
}
}

编辑:每个OP建议的修改代码表现出相同的延迟增加:

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

public class Locker {
    static final int WARMUP = 20000;
    final Object readLock = new Object();
    final Object writeLock = new Object();
    final Object stateLock = new Object();

    long interval = 1;
    int totalMessagesSent;
    long totalOps;
    long readerThread;
    long writerThread;
    final long[] measures = new long[WARMUP + 20];

    static long busySleep(long t) {
        long until = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(t);
        while(System.nanoTime() < until);
        return until;
    }
    void printResults(long latency, long timeToRead) {
        if (readerThread != 0 || writerThread != 0 || totalMessagesSent  > totalOps || timeToRead < 0) throw new Error();
        measures[totalMessagesSent] = latency;
    }

    void run() {
        while(totalMessagesSent < measures.length) {
            busySleep(interval);
            long timeSent = System.nanoTime();
            try {
                synchronized (writeLock) {
                    synchronized (stateLock) {
                        writerThread = Thread.currentThread().getId();
                    }
                    totalOps++;
                }
            }
            finally {
                synchronized (stateLock) {
                    writerThread = 0;
                }
            }
            long timeToRead = System.nanoTime();
            try {
                synchronized (readLock) {
                    synchronized (stateLock) {
                        readerThread = Thread.currentThread().getId();
                    }
                    totalOps++;
                }
            } finally {
                synchronized (stateLock) {
                    readerThread = 0;
                }
            }
            long now = System.nanoTime();
            timeToRead = now - timeToRead;
            long latency = now - timeSent;
            printResults(latency, timeToRead);
            ++totalMessagesSent;
            this.interval = (totalMessagesSent/WARMUP * 5000) + 1;
        }
        System.out.println("last measures = " + Arrays.toString(Arrays.copyOfRange(measures, WARMUP - 20, measures.length - 1)));
    }

    public static void main(String[] args) {
        new Locker().run();
    }
}