Short update:
I’m likely to sink my teeth into the meaty details before getting it actually done (the opposite of “first make it work, then make it fast”). So I tried to get moar dps (decals per second) out of it by optimizing multi-threading. The cropping is per triangle which are independent from each other. So I can easily spread the triangles from the input mesh across all available CPU threads. But getting a benefit wasn’t as easy as I thought.
What I’ve learned so far:
- avoid creating new temporary objects
- synchronized(buffer) { buffer.put(value) } is a bad idea
- avoid copying data
Point 1 is solved by something like jme3’s TempVars. Point 2 is solved by not synchronizing but combining the partial results afterwards. Point 3 is the biggest problem. Since I don’t know the final size of the buffer, it is inefficient to use buffers since growing a buffer means reallocating and copying data. Using ArrayList has the same problem since it copies on grow and there’s also the object overhead (unfortunately, Java doesn’t support primitives as generics).
I came up with the solution of writing my own “DynamicFloatArray” which doesn’t copy on grow but uses a pyramid of float[]. My first attempt was to implement it as a custom java.nio.FloatBuffer, but then I realized the package private constructor and that adding classes to java.nio package is not allowed. For that reason, I have to copy from the DynamicFloatArray to the FloatBuffer of the decal mesh (reusing the buffer if possible). I guess I can’t convince the devs to switch to Apache Mina Buffers?
Anyway, I might have missed something that could give me an additional boost. Therefore, I’ll append my DynamicFloatBuffer for review (see the add() method). I’m able to utilize my quad core up to 80% and increase the decals update rate by factor 2.2 compared to single threaded.
I’ve also tested the MemoryUtils of @pspeed. All three values are steadily growing while I run my decals test which updates a decal mesh as fast as possible. Does that mean I have some kind of memory leak? The memory usage in the task manager is stable (thanks to TempVars and buffer reusing) and I’m not using direct buffers as far as I know.
Edit: The java bbcode eats “lower than” and “greater than”. The ArrayList is generic of type float[].
Edit: The java bbcode also eats inner classes. I’ll try to post it different.
[java]
package decals;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.util.ArrayList;
/**
*
-
@author survivor
*/
public class DynamicFloatArray
{
private int length;
private int currentIndex;
private int currentArrayIndex;
private float[] currentArray;
private ArrayList arrays;
public DynamicFloatArray()
{
this(16);
}
public DynamicFloatArray(int baseCapacity)
{
this.arrays = new ArrayList();
this.arrays.add(new float[baseCapacity]);
this.reset();
}
public DynamicFloatArray add(float value)
{
this.length++;
this.currentIndex++;
if (this.currentIndex >= this.currentArray.length)
{
this.currentIndex = 0;
this.currentArrayIndex++;
if (this.currentArrayIndex > 1);
this.currentArray = new float[newCapacity];
this.arrays.add(this.currentArray);
}
}
this.currentArray[this.currentIndex] = value;
return this;
}
public float get(int index)
{
int currIdx = index;
int currArrIdx = 0;
float[] currArr = this.arrays.get(currArrIdx);
while (currIdx >= currArr.length)
{
currIdx -= currArr.length;
currArrIdx++;
currArr = this.arrays.get(currArrIdx);
}
return currArr[currIdx];
}
public final DynamicFloatArray reset()
{
this.length = 0;
this.currentIndex = -1;
this.currentArrayIndex = 0;
this.currentArray = this.arrays.get(0);
return this;
}
public DynamicFloatArray clear()
{
this.reset();
this.arrays.clear();
this.arrays.add(this.currentArray);
return this;
}
public FloatBuffer toFloatBuffer()
{
return this.toFloatBuffer(null);
}
public FloatBuffer toFloatBuffer(FloatBuffer result)
{
if (result == null || result.remaining() < this.length)
{
result = BufferUtils.createFloatBuffer(this.length);
}
int currIdx = 0;
int currArrIdx = 0;
float[] currArr;
while (currArrIdx = this.currArr.length)
{
this.currIdx -= this.currArr.length;
this.currArrIdx++;
this.currArr = arrays.get(this.currArrIdx);
}
this.pos++;
return this.currArr[this.currIdx++];
}
public boolean hasNext()
{
return this.pos = this.currArr.length)
{
this.currIdx -= this.currArr.length;
this.currArrIdx++;
this.currArr = arrays.get(this.currArrIdx);
}
this.pos++;
return this.currArr[this.currIdx++];
}
public boolean hasNext()
{
return this.pos = this.currArr.length)
{
this.currIdx -= this.currArr.length;
this.currArrIdx++;
this.currArr = arrays.get(this.currArrIdx);
}
this.pos++;
return this.currArr[this.currIdx++];
}
public boolean hasNext()
{
return this.pos < length;
}
}
}
[/java]
public int getLength()
{
return this.length;
}
public Enumerator getEnumerator()
{
return new Enumerator();
}
public class Enumerator
{
private int pos;
private int currIdx;
private int currArrIdx;
private float[] currArr;
private Enumerator()
{
this.reset();
}
public final void reset()
{
this.pos = 0;
this.currIdx = 0;
this.currArrIdx = 0;
this.currArr = arrays.get(this.currArrIdx);
}
public float next()
{
if (this.currIdx >= this.currArr.length)
{
this.currIdx -= this.currArr.length;
this.currArrIdx++;
this.currArr = arrays.get(this.currArrIdx);
}
this.pos++;
return this.currArr[this.currIdx++];
}
public boolean hasNext()
{
return this.pos < length;
}
}
}