FastSuite

FastSuite

18M Downloads

[Optimization] Small Optimization suggestion

Speiger opened this issue · 3 comments

commented

o/
Just checked out mods to replace optifine and found this gem.
I really like the idea of the Recipe access sorting Approach.

But the downside is that everything is a "Wrapper" or a "Pointer to a Pointer" and iteration is not really quick.

So I have a suggestion since I kinda wrote my FastUtil replacement and this is actually a really good indicator for that.

Instead of having linked nodes you just use the Recipe array and have a long array next to that Recipe array.
The long array would be basically a "Next/Prev" index indicator. (Prev would be the left 32bits, and next would be the right 32 bits)
All you would need is then a first/last index entry and then you could iterate like this:

public class MyRecipeList<T>
{
	Recipe<T>[] recipes;
	long[] links;
	int firstIndex;
	int lastIndex;
	
	public MyRecipeList(Recipe<T>[] recipes) {
		this.recipes = recipes;
		links = new long[recipes.length];
		firstIndex = 0;
		lastIndex = recipes.length-1;
		links[0] = 0xFFFFFFFF00000000L | 0x1; //Left = -1, Right = 1
		links[recipes.lengt-1] = (((recipes.length-2) & 0xFFFFFFFFL) << 32) | 0xFFFFFFFFL; //Left prelastIndex, right -1
		for(int i = 1,m=recipes.length-1;i<m;i++) {
			links[i] = (((i-1) & 0xFFFFFFFFL) << 32) | ((i+1) & 0xFFFFFFFFL);
		}
	}
	
	public Recipe<T> findRecipe(T inv, Level world) {
		int index = firstIndex;
		while(index != -1) {
			Recipe<T> recipe = recipes[index];
			if(recipe.matches(inv, world) {
				moveToFirst(index);
				return recipe;
			}
			index = (int)links[index];
		}
		return null;
	}
	
	public void moveToFirst(int index) {
		if(index == firstIndex) return;
		long link = links[index];
		int prev = (int)(link >>> 32);
		int next = (int)link;
		links[prev] ^= ((links[prev] ^ (link & 0xFFFFFFFFL)) & 0xFFFFFFFFL);
		links[next] ^= ((links[next] ^ (link & 0xFFFFFFFF00000000L)) & 0xFFFFFFFF00000000L);
		
		links[firstIndex] ^= ((links[firstIndex] ^ ((index & 0xFFFFFFFFL) << 32)) & 0xFFFFFFFF00000000L);
		links[index] = 0xFFFFFFFF00000000L | (firstIndex & 0xFFFFFFFFL);
		firstIndex = index;
	}
}

This iterator is not only a lot faster (10-15%, Look at IterateForEach) but also moving around recipes goes a lot quicker, because you are only manipulating a auxiliary array which is just a primitive type, which means you don't move around memory addresses each time you try to resort the array, instead you stay within the contained array. Resulting in 0 performance problems, instead of scattering the ram.

If you are not interested, close the issue, no hard feelings, I just saw this and got a itch to optimize it, and cause a bit cleaner code too.

Small edit:
This code was written on 4AM in the middle of the night, not tested and copy pasted together, so the quality is shoddy at the very least, but i am 80% sure it would run without flaws. But to TLDR It. EXAMPLE CODE, DO NOT USE UNTESTED!

commented

The thing that fastsuite does of importance is providing recently used recipes at the front of the list, reducing match time to hit them.

I'll give this some thought, but iteration time isn't really a bottleneck here - the bottleneck is Recipe#matches

commented

That is true, but this is not only a CPU Optimization, but also a Ram reducer since you are not dealing with wrappers to every single recipe, so you cut your memory footprint in half, or even less since you are just recycling data at that point.

commented

Ultimately ended up moving to an async matching approach in 4.1.0, so linkedlist stuff is a thing of the past.