As my game is already a bit too big and I’ve still got a couple things to add, I thought I’d ask a few optimization questions (seeing as this is my first 4k attempt). I know how to program in Assembly, so I’m keeping some of that in mind (like what seems like it will take more jumps, labels, etc.), but I don’t know how to view the Java Assembly-ish byte code I’ve seen people post sometimes. How do I do that?
Which of the following take up the least space?
myGuyX += 1000;
myGuyY += 1000;
or
int change = 1000;
myGuyX += change;
myGuyY += change;
or
myGuyX += 100*10;
myGuyY += 100*10;
or
int change = 100*10;
myGuyX += change;
myGuyY += change;
Or if none of those are the best way, what is? I just noticed that in my program I have several numbers typed multiple times, especially 1024. Will storing it be better or worse for the class size? What about adding in an operation so that both operands are less than 256?
Using java.shape.contains() versus a simple bounding box check (i.e. x1 <= x2 + width2 && x1 + width1 >= x2 && y1 <= y2 + height2 && y1 + height1 >= y2); which is smaller? I notice in the class file any method I call ends up having its string stored in the class file, as well as a string of the path of the class that has that function. That’s often like 20 letters for one function. In general, if I can do a bunch of operations (same goes for Math.min and Math.max) does it make more sense to do that instead of calling the function?
Does multiple if statements versus if/else statements save any space? (i.e. if (a <= 0) {} if (a > 0 && a <= 10) {} instead of if (a <= 0) {} else if (a <= 10))
What’s the smallest way of doing loops? for vs while vs do/while. I’m doing typical array access, so I’m using for loops. I was originally using do/while because someone had mentioned that was cheaper, but it was confusing and I had a lot of looper variables sticking around.
Right now I’m using all Java’s own drawing methods, namely fillOval, fillRect, etc. This means I don’t need to import or use ImageIO, but it also means I’m drawing a lot of ovals. Here is the code for drawing the character:
//Draw the character.
if (i == 0)
{
g.setColor(Color.WHITE);
}
else
{
g.setColor(Color.GRAY);
}
g.fillOval((int)fieldXs[i]-5,(int)fieldYs[i]-6,11,13);
g.fillOval((int)fieldXs[i]-3,(int)fieldYs[i]-12,6,6);
if (walkStep || i != 0)
{
g.fillOval((int)fieldXs[i]-8,(int)fieldYs[i]-6,3,15);
g.fillOval((int)fieldXs[i]+5,(int)fieldYs[i]-6,3,12);
g.fillOval((int)fieldXs[i]-4,(int)fieldYs[i]+2,3,13);
g.fillOval((int)fieldXs[i]+1,(int)fieldYs[i]+2,3,16);
}
else
{
g.fillOval((int)fieldXs[i]-8,(int)fieldYs[i]-6,3,12);
g.fillOval((int)fieldXs[i]+5,(int)fieldYs[i]-6,3,15);
g.fillOval((int)fieldXs[i]-4,(int)fieldYs[i]+2,3,16);
g.fillOval((int)fieldXs[i]+1,(int)fieldYs[i]+2,3,13);
}
As you can see, I’ve got a lot of coordinate stuff in there and arbitrary numbers, as well as calling 10 different methods and two sets of if/else statements. Drawing an image would be one single g.drawImage and would have less if statements (potentially). But, I would need a try/catch and ImageIO.read, as well as a small and well compressed image. Can those get small enough to make it worth it? I’m also devoting 8 lines of code (6 draws and 2 color changes) to draw the ghosts.
What’s the cost of a few if statements versus another for loop? Currently I’m doing all drawing and logic in the same loops, and that involves a lot of if (i == 0) {} for things that should only happen once. This is saving me on lines of code, but I’m wondering it’s actually costing me more bytes.
How much space does typecasting take up?
8 ) Is there a reason to use floats versus doubles? If it’s cheaper to use floats, will it still be cheaper even though I have to typecast sometimes?
How costly is array access? Not, very, right? It wouldn’t make sense to assign a temporary variable to hold the data if I do array access say 4 times, would it?
Is it cheaper to say array.length or the length number if I know what it is? Like int[] arr = new int[500]; Is for (int i = 0; i < arr.length; i++) cheaper or is for (int i = 0; i < 500; i++) cheaper?
That’s all I can think of for now. Thanks a lot for your answers!
That’s loads of questions! I’ll try answering, but keep in mind it’s all AFAIK:
1) Which of the following take up the least space?
Almost certainly this one:
myGuyX += 1000;
myGuyY += 1000;
It’s a very good idea to try to write repetitive code, as that helps the zipping.
2) Using java.shape.contains() versus a simple bounding box check (i.e. x1 <= x2 + width2 && x1 + width1 >= x2 && y1 <= y2 + height2 && y1 + height1 >= y2); which is smaller?
That depends on how often you use it and what the check is. If it’s just a single line of code, doing it manually is a LOT smaller as method calls add a whole bunch of garbage the constant pool.
The flip side is that once you’ve used a function call, using it again later on is much cheaper, possibly cheaper than the manual intersect check.
3) Does multiple if statements versus if/else statements save any space? (i.e. if (a <= 0) {} if (a > 0 && a <= 10) {} instead of if (a <= 0) {} else if (a <= 10))
Shouldn’t matter much at all. You could save a few bytes by fiddling around with if statement logic, but it’s highly situational.
4) What’s the smallest way of doing loops? for vs while vs do/while. I’m doing typical array access, so I’m using for loops. I was originally using do/while because someone had mentioned that was cheaper, but it was confusing and I had a lot of looper variables sticking around.
… no idea, sorry.
5) Right now I’m using all Java’s own drawing methods, namely fillOval, fillRect, etc. […] But, I would need a try/catch and ImageIO.read, as well as a small and well compressed image. Can those get small enough to make it worth it?
This totally depends on how fancy graphics you want. If you want bitmap quality graphics, you’re either going to have to do pixel level synthesis, or load bitmap data somehow.
The upside of generated graphics is that it’s very cheap to add variations of the graphics, like having ghosts get bigger with each level.
I usually go for pixel generated graphics.
6) What’s the cost of a few if statements versus another for loop?
That depends on how similar the loops would be if separated. Try it (and zip the results), and see if it’s worth it.
I usually have a single loop and try to optimize away special cases.
7) How much space does typecasting take up?
Depends on what type it is. Most primitive conversions is a single byte per cast.
Casting objects requires the class name to be in the constant pool (of course), and a couple of bytes.
8 ) Is there a reason to use floats versus doubles? If it’s cheaper to use floats, will it still be cheaper even though I have to typecast sometimes?
long version: doubles and longs take up two slots in the stack, meaning the compiler has to use different instructions for certain actions (dup and stack reordering). These instructions aren’t bigger than the original ones, but if you’re mixing the two types, zipping might get less efficient.
short version: try it! My guess is that whatever causes the least typecasts will win.
9) How costly is array access?
More than a local variable lookup, but repeated code usually gets zipped away, so you usually don’t gain much from storing an array entry.
10) Is it cheaper to say array.length or the length number if I know what it is?
It’s cheaper to say the number.
Hehe, posted just after the ones above - there’s probably quite abit of duplication
Javac evaluates composition of constants during compilation, therefore 10*100 will be evaluated to 1000.
This means code examples a & c will generate identical bytecode, as will examples b & d.
In addition, because your intermediary ‘int change’ is assigned to only once, and given a constant value, most good optimisers will entirely eliminate it.
If you changed it’s declaration to ‘final int change = …’ javac too would make this same optimisation. (this is good programming practice in any case)
Therefore, all 4 examples should be considered as generating identical bytecode when using the right compilation tools.
yep, every reference to an external class member requires a number of constant pool entries.
It becomes a trade-off based upon how much work the method does for you, and how many invocations are made.
[quote]3) Does multiple if statements versus if/else statements save any space? (i.e. if (a <= 0) {} if (a > 0 && a <= 10) {} instead of if (a <= 0) {} else if (a <= 10))
[/quote]
Generally no.
Just make sure the logic done in the loop counter is equivalent, and it should result in the same bytecode.
Having said that while & for loops contain an implicit unconditional jump at the end of the loop. do/while loops do not have this (because the loop condition is already at the end of the loop), and therefore will be smaller.
Ofcourse, a do/while won’t always fit with the logic performed by the loop. (for & while loop 0 or more times, do/while loops 1 or more times)
e.g.
while sudo-code
1 X > Y ? GOTO 5
2 // statement in loop
3 // statement in loop
4 GOTO 1
5 // code beyond loop
do/while sudo-code
1 // statement in loop
2 // statement in loop
3 // X <= Y ? GOTO 1
4 // code beyond loop
If you can get away without using images, you should. As you’ve already mentioned, it avoids pulling in a load of api methods.
Though for future reference it’s worth knowing you can just declare main as ‘throws Throwable’, rather than trying to handle any Exceptions.
Regarding your specific example, you should eliminate all of the repeated accesses to the array - accessing an array element requires significantly more bytecode than a local variable.
You should use the ternary operator for your setColor call -> “g.setColor(i==0?Color.WHITE:Color.GRAY);”
Also, if you are accessing more than 1 of the named Color instances, you should change to using the constructor “new Color(int)”, as each reference to a named Color instance increases the constants pool size.
As a general rule, special-casing the ‘i==0’ will give you smaller bytecode.
Depends what you are typecasting between?
primitive types? tiny, 1 byte.
object types? more. Though I doubt you will be doing this often in a 4kb app.
[quote]8 ) Is there a reason to use floats versus doubles? If it’s cheaper to use floats, will it still be cheaper even though I have to typecast sometimes?
[/quote]
Wide primitive constants (double & long) occupy twice the number of bytes in the constants pool, occupy two constants pool indexes, and can need larger instructions to access them. 3 good reasons to avoid them if possible.
Ofcourse, as with many aspects of 4kb it’s a trade-off, because most of the Java API methods (java.awt.geom, java.lang.Math etc) operate on doubles.
So, if you use floats you reduce constants pool size, but end up having to add bytes for casting to & from any API methods you use.
The exact number of bytes can vary tremendously, but access to array elements is always larger than local variables.
If you read from an element more than once, I’d suggest you store it in a [final] local variable.
It’s much easier for an optimiser to undo any overzealous common subexpression elimination you’ve put in, than it is for the optimiser to work it out itself.
Having said that, it gets abit more complicated if you are writing to the array elements as well as reading.
[quote]10) Is it cheaper to say array.length or the length number if I know what it is? Like int[] arr = new int[500]; Is for (int i = 0; i < arr.length; i++) cheaper or is for (int i = 0; i < 500; i++) cheaper?
[/quote]
If the length value can be evaluated to a constant, it will (for all but the most fantastical of situations!) be smaller to use the constant value.
If the length is a variable determined at runtime, the answer isn’t so clear.
The only thing left to say I think, is not to worry about micro-optimisations until your game is done.
If you try and keep your game below 4kb every time you add a new feature, you will waste hours.
Write your game with optimal size in-mind, but don’t go back and optimise anything until the games fundamentals are done.
:edit:
Actually, after reading Markus’s post, there is 1 more thing to say.
The unpredictability of zipping fcuks up alot of micro-optimisations, another reason to avoid doing them until the very end!
Yeah, I’ve been finding this a lot. I’ve left it until the end to do the more crazy/unreadable optimisations and I’m finding that most of them either don’t have any effect or have unpredictable effects after running moogie’s compacting tool. In particular I’ve been finding that removing/reducing the number of float->int casts has had no effect, and neither has reusing local variables. Seems that the optimisers are pretty good at compacting functionally identical code down to the smallest common representation.
My game is now pretty much done and is I think at least 5000 bytes without any optimizers or extra compression. So I’ll experiment with all this and see if I can get it down lower.