[CLOSED] (LWJGL3.0.0a) Visible Performance Regression from C GLFW to Java GLFW

Started by Xirema, May 11, 2015, 22:01:01

Previous topic - Next topic

Xirema

Based on the tutorial provided on GLFW.org's website, I assembled a basic Hello World animation to test that I had set up my (Eclipse and Visual Studio) environments correctly. I've included here the entirety of the code for both versions of the code, and it should be clear immediately that the differences between the two are cosmetic at most.

Java:
package test;

import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;

import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GLContext;

public class GLFWTesting2 {
	static void error_callback(int error, long description)
	{
		//fputs(description, stderr);
	}
	static void key_callback(long window, int key, int scancode, int action, int mods)
	{
		if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
			glfwSetWindowShouldClose(window, GL_TRUE);
	}
	public static void main(String[] args)
	{
		long window;
		glfwSetErrorCallback(GLFWErrorCallback(GLFWTesting2::error_callback));
		if (glfwInit() == 0)
			System.exit(1);
		window = glfwCreateWindow(640, 480, "Simple example (Java)", 0, 0);
		if (window == 0)
		{
			glfwTerminate();
			System.exit(1);
		}
		glfwMakeContextCurrent(window);
		GLContext.createFromCurrent();
		glfwSwapInterval(1);
		glfwSetKeyCallback(window, GLFWKeyCallback(GLFWTesting2::key_callback));
		while (glfwWindowShouldClose(window) == 0)
		{
			float ratio;
			IntBuffer widthb = BufferUtils.createIntBuffer(1), heightb = BufferUtils.createIntBuffer(1);
			int width, height;
			glfwGetFramebufferSize(window, widthb, heightb);
			width = widthb.get(0);
			height = heightb.get(0);
			ratio = width / (float)height;
			glViewport(0, 0, width, height);
			glClear(GL_COLOR_BUFFER_BIT);
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			glOrtho(-ratio, ratio, -1.f, 1.f, 1.f, -1.f);
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
			glRotatef((float)glfwGetTime() * 50.f, 0.f, 0.f, 1.f);
			glBegin(GL_TRIANGLES);
			glColor3f(1.f, 0.f, 0.f);
			glVertex3f(-0.6f, -0.4f, 0.f);
			glColor3f(0.f, 1.f, 0.f);
			glVertex3f(0.6f, -0.4f, 0.f);
			glColor3f(0.f, 0.f, 1.f);
			glVertex3f(0.f, 0.6f, 0.f);
			glEnd();
			glfwSwapBuffers(window);
			glfwPollEvents();
		}
		glfwDestroyWindow(window);
		glfwTerminate();
		System.exit(0);
	}
}


C:
#include <GLFW/glfw3.h>
#include <stdlib.h>
#include <stdio.h>
static void error_callback(int error, const char* description)
{
	fputs(description, stderr);
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GL_TRUE);
}
int main(void)
{
	GLFWwindow* window;
	glfwSetErrorCallback(error_callback);
	if (!glfwInit())
		exit(EXIT_FAILURE);
	window = glfwCreateWindow(640, 480, "Simple example (C)", NULL, NULL);
	if (!window)
	{
		glfwTerminate();
		exit(EXIT_FAILURE);
	}
	glfwMakeContextCurrent(window);
	glfwSwapInterval(1);
	glfwSetKeyCallback(window, key_callback);
	while (!glfwWindowShouldClose(window))
	{
		float ratio;
		int width, height;
		glfwGetFramebufferSize(window, &width, &height);
		ratio = width / (float)height;
		glViewport(0, 0, width, height);
		glClear(GL_COLOR_BUFFER_BIT);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(-ratio, ratio, -1.f, 1.f, 1.f, -1.f);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glRotatef((float)glfwGetTime() * 50.f, 0.f, 0.f, 1.f);
		glBegin(GL_TRIANGLES);
		glColor3f(1.f, 0.f, 0.f);
		glVertex3f(-0.6f, -0.4f, 0.f);
		glColor3f(0.f, 1.f, 0.f);
		glVertex3f(0.6f, -0.4f, 0.f);
		glColor3f(0.f, 0.f, 1.f);
		glVertex3f(0.f, 0.6f, 0.f);
		glEnd();
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	glfwDestroyWindow(window);
	glfwTerminate();
	exit(EXIT_SUCCESS);
}


Both compile correctly and run correctly. However, I have noticed that in the animation rendered by the Java code, there is visible stuttering in the animation, whereas in the animation rendered by the C code, this stuttering is eliminated. I encourage anyone wishing to verify/contradict my results compile these code snippets themselves; it took me longer to compose this post than it took to properly set up my environments.

Now, I am aware that Java is slower than C, and that there's a cost incurred by switching from Java to native code. But is that really enough to explain the difference in performance, or is something else at play?

spasi

One thing you need to be careful in Java is buffer allocations (line 40 in your code). In C, the width/height variables are simple integers on the stack. In Java, you're effectively paying the price of off-heap malloc + IntBuffer heap allocation, on every frame, and eventually GC + off-heap cleanup for previous frames. See if moving line 40 outside the render loop eliminates the stuttering.

Even with the above though, I don't see how such a simple program could stutter. You're enabling vsync and 60x2 IntBuffer allocations per second should be no problem. Could you share some details about your machine and environment (GPU, OS, JVM version, etc)? I don't see any stuttering whatsoever on my machine.

Cornix

Quote from: Xirema on May 11, 2015, 22:01:01Now, I am aware that Java is slower than C
Out of curiosity, why would you think that? Maybe this was true several years ago, but I personally get extremely fast java programs once the JIT wakes up and does its job.
Its like saying a hammer is slower then a screw driver.

abcdef

The demo code runs stutter free for me, running on a laptop with intel card.

Xirema

Quote from: Cornix on May 12, 2015, 09:45:08
Quote from: Xirema on May 11, 2015, 22:01:01Now, I am aware that Java is slower than C
Out of curiosity, why would you think that? Maybe this was true several years ago, but I personally get extremely fast java programs once the JIT wakes up and does its job.
Its like saying a hammer is slower then a screw driver.

I've done performance testing between Java 8 and C, and I still see noticeable differences in speed between the two languages.

It's not orders of magnitude of difference, but it's still substantial enough for me to panic every single time I see performance regressions.

Quote from: spasi on May 11, 2015, 22:39:45
Even with the above though, I don't see how such a simple program could stutter. You're enabling vsync and 60x2 IntBuffer allocations per second should be no problem. Could you share some details about your machine and environment (GPU, OS, JVM version, etc)? I don't see any stuttering whatsoever on my machine.
Quote from: abcdef on May 12, 2015, 18:56:31
The demo code runs stutter free for me, running on a laptop with intel card.

So there's a few things I found interesting about my tests today:

  • When I first started running tests, it looked like the stuttering had stopped entirely. UNTIL.....
  • .... I had let the program run a few minutes, at which point the CPU fan kicked into its highest level. At this point, the C code continued to run smoothly, while the Java code began to exhibit the stuttering again.
I would also point out that the stuttering is extremely difficult to see if you don't have both versions of the program propped up side-by-side (I made sure to keep them on the same monitor, naturally). It's no more than one or two frames at a time that get dropped.

Now for the fun part:
Quote from: spasi on May 11, 2015, 22:39:45
One thing you need to be careful in Java is buffer allocations (line 40 in your code). In C, the width/height variables are simple integers on the stack. In Java, you're effectively paying the price of off-heap malloc + IntBuffer heap allocation, on every frame, and eventually GC + off-heap cleanup for previous frames. See if moving line 40 outside the render loop eliminates the stuttering.
This seems to have worked. I actually feel kind of silly I didn't think about that, but it definitely seems like constantly creating new buffers was the problem.

Your observation that the GC might be to blame is what I think is most accurate: It wasn't /every/ frame that was slow, it was periodic, random frames that seemed slow, and I'm not observing those slowdowns anymore.

Is there a way to mark my original post as resolved, so it doesn't clutter up the forum?

Cornix

Quote from: Xirema on May 12, 2015, 20:58:25

I've done performance testing between Java 8 and C, and I still see noticeable differences in speed between the two languages.

It's not orders of magnitude of difference, but it's still substantial enough for me to panic every single time I see performance regressions.
It really depends on the programs you are testing. Java byte code is being compiled to machine code on the run by the JIT, there should be absolutely no difference in speed for simple computations, perhaps the java code is even faster because the JIT can do run-time optimizations.
I was recently discussing this topic with a colleague of mine and we tested a simple program that was actually (on average) faster in Java then the C equivalent because of the run-time optimizations.

Quote from: Xirema on May 12, 2015, 20:58:25I would also point out that the stuttering is extremely difficult to see if you don't have both versions of the program propped up side-by-side (I made sure to keep them on the same monitor, naturally). It's no more than one or two frames at a time that get dropped.
If you have both versions running and both use VSync then you got a race-condition. Both programs try to synchronize on the same resource and it is unavoidable that one or both will become slow. You need to test each one independently and make sure that no other program with VSync is running at the same time.

Quote from: Xirema on May 12, 2015, 20:58:25This seems to have worked. I actually feel kind of silly I didn't think about that, but it definitely seems like constantly creating new buffers was the problem.

Your observation that the GC might be to blame is what I think is most accurate: It wasn't /every/ frame that was slow, it was periodic, random frames that seemed slow, and I'm not observing those slowdowns anymore.
That is a very common kind of problem when you are not used to programming with java. Happens to everybody now or then.