3

I have a pretty straight forward server (using kryonet). The clients are only storing the current state of the car (x,y,angle etc) and are sending requests of acceleration and turning.

The server is receiving the requests and adding them to a ArrayBlockingQueue that the physics thread drains and reads and updates.

When adding another player, the speed of the game slows down by almost double. I have ruled out a lot of things (I have all updates and package sending throttled at 60Hz.)

I am suspecting that using a blocking queue is blocking so much that it is causing the slowdown.

How can I send the client requests to the physics thread without blocking issues?

Ólafur Waage
  • 68,817
  • 22
  • 142
  • 198
  • Let me get this straight: You want to pass 60 messages a second using a blocking queue, and think it the bottleneck?? – meriton Nov 07 '11 at 00:01
  • I don't understand your question, I'm pretty new to blocking lists so please don't act this way. – Ólafur Waage Nov 07 '11 at 00:06
  • If the queue isn't filling, then you're not blocking. Is your CPU running at 100%? How many players do you have at the start when you added one? – David Harkness Nov 07 '11 at 00:22
  • CPU stands at around 55% (even when adding players). 1 player is currently 5 physics bodies. I have 1 player at the start, then I run another client to connect to the server (so I have 10 bodies simulating). When I do that, the acceleration speed of the cars is half as much as it was. – Ólafur Waage Nov 07 '11 at 00:27
  • Are you running a multicore processor? If so, the physics thread will be limited to a single core. It can consume 100% of that core which will account for 50% of a dual-core CPU. – David Harkness Nov 07 '11 at 00:40

5 Answers5

2

I am suspecting that using a blocking queue is blocking so much that it is causing the slowdown.

You suspect wrong. The following test program pushes 1 million Integers through an ArrayBlockingQueue:

public class ArrayBlockingQueuePerfTest {
    int maxi = 1000000;

    ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(1000,
            true);

    Thread sender = new Thread("sender") {
        public void run() {
            try {
                for (int i = 0; i < maxi; i++) {
                    queue.offer(i, 1, TimeUnit.SECONDS);
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
    };
    Thread receiver = new Thread("receiver") {
        public void run() {
            try {
                int count = 0;
                long sum = 0;
                while (count < maxi) {
                    sum += queue.poll(1, TimeUnit.SECONDS);
                    count++;
                }
                System.out.println("done");
                System.out.println("expected sum: " + ((long) maxi) * (maxi - 1) / 2);
                System.out.println("actual sum:   " + sum);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
    };

    public ArrayBlockingQueuePerfTest() {
        sender.start();
        receiver.start();
    }

    public static void main(String[] args) {
        new ArrayBlockingQueuePerfTest();
    }
}

On my laptop, it terminates in a couple seconds. So wherever your performance bottleneck is, it is not the ArrayBlockingQueue, which could handle a throughput at least 3 orders of magnitude higher than you need. Put differently, even if you found a thread communication approach that takes no execution time at all, that would only speed up your program by at most 0.1%.

Take home lesson for this and all other performance problems: The first step when tackling any performance problem in existing code is to measure which part of the code is slow, as usually, it is not where one expects. Profilers simplify this task greatly.

meriton
  • 68,356
  • 14
  • 108
  • 175
  • I have profiled the code through the JVM profiler, I have big logs and they are currently showing me nothing. When adding the 2nd player there is no difference, no spike. I'm suspecting now that the problem is not with the server, but with the client. But thanks so much for your reply. – Ólafur Waage Nov 07 '11 at 23:20
1

You can use a disruptor (ring buffer), a lock-free mechanism for implementing a queue. See:

Emmanuel Bourg
  • 9,601
  • 3
  • 48
  • 76
  • Using a ring buffer (the heart of the disruptor) would eliminate tiny amount of time taken with concurrency locking, but I can't imagine that time would cause a 50% slowdown with an extra 60 messages per second. – David Harkness Nov 07 '11 at 00:23
1

I have found the bug. I needed to throttle the physics simulation in a different way (not with the world.step() function, but limit how often that is called). It is something like this.

while(true)
{
    delta = System.nanoTime() - timer;

    if(delta >= 16666666) // 60 Hz
    {
        world.step(1.0f, 6, 2);

        processAndUpdateYourData();

        timer = System.nanoTime();
    }
}

Then I need to adjust all the physics numbers so they feel natural with this configuration.

Ólafur Waage
  • 68,817
  • 22
  • 142
  • 198
0

Your question presupposes an implementation - you would be better to ask "why is my code so slow?".

The blocking queue is the most efficient way of implementing the producer/consumer pattern.

I would add more consumer threads - try adding as many consumer threads as there are processor cores - ie Runtime.getRuntime().availableProcessors().

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • I can not add more threads to the server side of things, since the receiver of client packets is implemented by the kryonet library. And the physics thread needs to be 1 thread to simulate the world. – Ólafur Waage Nov 06 '11 at 23:47
0

Make sure you are creating the queue with enough slots available to satisfy all the clients. If the queue becomes full because there are too many clients, they will block when trying to insert commands. If that's not the problem it means your physics (consumer) thread is not keeping up with the requests and you need to make sure it gets more processing time.

David Harkness
  • 35,992
  • 10
  • 112
  • 134
  • I can see if the queue get's too full. It will throw an exception and crash the client (currently). And it's not happening at this moment. – Ólafur Waage Nov 06 '11 at 23:59