Hehe, posted just after the ones above - there's probably quite abit of duplication

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?
1) Which of the following take up the least space?
1 2
| myGuyX += 1000; myGuyY += 1000; |
or
1 2 3
| int change = 1000; myGuyX += change; myGuyY += change; |
or
1 2
| myGuyX += 100*10; myGuyY += 100*10; |
or
1 2 3
| 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?
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.
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? 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?
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.
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))
Generally no.
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.
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
5) 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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.
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.
6) 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.
As a general rule, special-casing the 'i==0' will give you smaller bytecode.
7) How much space does typecasting take up?
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.
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?
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.
9) 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?
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.
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?
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.
That's all I can think of for now. Thanks a lot for your answers!
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!
