有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java CPUWise,如何优化UDP数据包发送?

我目前有一个游戏,我为它实现了一个客户端和一个服务器

然后,我让服务器向客户端发送有关其位置的数据,客户端向服务器发送移动输入,等等

问题是CPU的使用率飙升至100%。我已将高使用率直接连接到以下代码,这些代码位于每秒调用十次的update()方法中:

try{
        sendToClientUDP(("ID:" + String.valueOf(uid)));
        sendToClientUDP(("Scale:" + GameServer.scale));

        for (Clients cl : GameServer.players){
            //sendToClient(("newShip;ID:" + cl.uid).getBytes(), packet.getAddress(), packet.getPort());
            sendToClientUDP((("UID:" + cl.uid +";x:" + cl.x)));
            sendToClientUDP((("UID:" + cl.uid +";y:" + cl.y)));
            sendToClientUDP((("UID:" + cl.uid +";z:" + cl.z)));
            sendToClientUDP((("UID:" + cl.uid +";Rotation:" + (cl.rotation))));
            cl.sendToClientUDP(new String("newShip;ID:" + uid));
            sendToClientUDP(new String("newShip;ID:" + cl.uid));
        }
        }catch (Exception e){
            e.printStackTrace();
        }

删除代码,高CPU使用率就会消失

这是我的sendToClientUDP()方法

public void sendToClientUDP(String str){
        if (!NPC){ //NPC is checking if it is a computer-controlled player.
        UDP.sendData(str.getBytes(), ip, port);
        }
    }

这是我的UDP。sendData()方法:

public static void sendData(String data, InetAddress ip, int port) {
    sendData(data.getBytes(), ip, port);
}

public static void sendData(byte[] data, InetAddress ip, int port) {
    DatagramPacket packet = new DatagramPacket(data, data.length, ip, port);
    try {
        socket.send(packet);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

为什么仅仅通过发送UDP数据包就占用了这么多CPU?如果有的话,我能做些什么来减少它


共 (1) 个答案

  1. # 1 楼答案

    我建议您删除或优化产生如此多CPU的代码,CPU分析器是最好的起点,但这些可能是CPU消耗的原因

    • 创建字符串和字节[]非常昂贵,我会避免这样做
    • 创建多个数据包而不是成批处理数据包也很昂贵
    • 可以避免创建新的DatagramPacket
    • 我会消除邮件之间的重复,因为这会增加您可以避免的冗余工作 您可以考虑使用二进制格式来避免转换到文本的转换开销。李>
    • 几乎从来没有一个好时机来使用new String()它几乎肯定是多余的

    编辑:这就是我的想法。不是每个客户端发送5个数据包,而是总共只发送一个数据包。对于10个客户端,您发送1/50的数据包,从而减少开销

    import java.io.IOException;
    import java.net.*;
    import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by peter on 31/07/15.
     */
    public class PacketSender {
        public static void main(String[] args) throws IOException {
            PacketSender ps = new PacketSender(InetAddress.getByName("localhost"), 12345);
            List<Client> clients = new ArrayList<>();
            for(int i=0;i<10;i++)
                clients.add(new Client());
    
            for(int t = 0; t< 3;t++) {
                long start = System.nanoTime();
                int tests = 100000;
                for (int i = 0; i < tests; i++) {
                    ps.sendData(1234, 1, clients);
                }
                long time = System.nanoTime() - start;
                System.out.printf("Sent %,d messages per second%n", (long) (tests * 1e9 / time));
            }
        }
    
    
        final ThreadLocal<ByteBuffer> bufferTL = ThreadLocal.withInitial(() -> ByteBuffer.allocate(8192).order(ByteOrder.nativeOrder()));
        final ThreadLocal<DatagramSocket> socketTL;
        final ThreadLocal<DatagramPacket> packetTL;
    
        public PacketSender(InetAddress address, int port) {
            socketTL = ThreadLocal.withInitial(() -> {
                try {
                    return new DatagramSocket(port, address);
                } catch (SocketException e) {
                    throw new AssertionError(e);
                }
            });
            packetTL = ThreadLocal.withInitial(() -> new DatagramPacket(bufferTL.get().array(), 0, address, port));
        }
    
        public void sendData(int uid, int scale, List<Client> clients) throws IOException {
            ByteBuffer b = bufferTL.get();
            b.clear();
            b.putInt(uid);
            b.putInt(scale);
            b.putInt(clients.size());
            for (Client cl : clients) {
                b.putInt(cl.x);
                b.putInt(cl.y);
                b.putInt(cl.z);
                b.putInt(cl.rotation);
                b.putInt(cl.uid);
            }
            DatagramPacket dp = packetTL.get();
            dp.setData(b.array(), 0, b.position());
            socketTL.get().send(dp);
        }
    
        static class Client {
            int x,y,z,rotation,uid;
        }
    }
    

    当这个性能测试运行时,它会打印出来

    Sent 410,118 messages per second
    Sent 458,126 messages per second
    Sent 459,499 messages per second
    

    编辑:要写/读文本,可以执行以下操作

    import java.nio.ByteBuffer;
    
    /**
     * Created by peter on 09/08/2015.
     */
    public enum ByteBuffers {
        ;
        /**
         * Writes in ISO-8859-1 encoding. This assumes string up to 127 bytes long.
         *
         * @param bb to write to
         * @param cs to write from
         */
        public static void writeText(ByteBuffer bb, CharSequence cs) {
            // change to stop bit encoding to have lengths > 127
            assert cs.length() < 128;
            bb.put((byte) cs.length());
            for (int i = 0, len = cs.length(); i < len; i++)
                bb.put((byte) cs.charAt(i));
        }
    
        public static StringBuilder readText(ByteBuffer bb, StringBuilder sb) {
            int len = bb.get();
            assert len >= 0;
            sb.setLength(0);
            for (int i = 0; i < len; i++)
                sb.append((char) (bb.get() & 0xFF));
            return sb;
        }
    
        private static final ThreadLocal<StringBuilder> SB = new ThreadLocal<>() {
            @Override
            protected Object initialValue() {
                return new StringBuilder();
            }
        };
    
        public static String readText(ByteBuffer bb) {
            // TODO use a string pool to reduce String garbage.
            return readText(bb, SB.get()).toString();
        }
    }
    

    如果你需要更复杂的东西,你应该考虑使用我写的^ a1}。是的

    • 支持64位内存大小,包括64位内存映射
    • 线程安全的堆外操作
    • 字符串的UTF-8编码
    • 压缩类型,例如停止位编码
    • 自动字符串池以减少垃圾
    • 通过引用计数对堆外资源进行确定性清理