Let's do this.

woah, thanks i wasn’t following most of these statements

You save even a little more space by manually removing the constants and putting the value in their place.

From the start you can program a little more normally, so it is easier to follow your own code. Then remove constants and replace them with their values. Don’t use temporary variables for calculations, just put the calculation inline. Don’t worry about using single letter variables, because an ofuscator will take care of that for you. It also removes class information for reflection, because you won’t need them.

[quote=“CaptainJester,post:22,topic:25385”]
If you do use a variable, however, don’t be a scrooge with them. Since local variables are all on the stack, it doesn’t matter to the compiled code if you reuse one variable or ten. It generates pretty much the same code. So I guess the other lesson to get out of this is to watch out for false economy. :slight_smile:

Actually, that’s a good trick, first obfuscating, and then repacking… saved me 0,1 kb or something, and that’s when the jar was only 2,5kb in total :smiley:

That is certainly a false economy - any halfway decent peep-hole optimiser will automagically inline such assignments.
(a value written to, and immediately read back from a local)

Keeping the number of entries in the constants pool to less than 256 will give a very real gain.
It will avoid use of the larger ldc_w instruction.
(and may also help compression, because you will potencially have one less unique symbol to compress)

Also, never use double or long constants - they use up 2 entries in the constants pool,
and are read from the pool using a different instruction.
It is best to store them as float/int and let them be cast upto the needed type.

A good example of this (as most will be using it ;)) is enableEvents(long).
If you simply pass in a constant to this method (regardless of whether it is an int or a long),
it will be expanded to a long by javac, and stored in the constants pool as such.

It is better to store the value in the constants pool as an integer, and then cast it to a long when it is passed into the method.
However, i’m unsure if you can perform this optimisation using the Java syntax;
or whether the java compiler will always evaluate the constant, and extend it to a long.
(it is certainly an optimisation worth automating with BCEL)

[crazyness]
infact, if you are using a small value (10 or less) for the enableEvents flag (for example 6 ;)) - the optimal solution turns out to be enableEvents((long)(3+3)) as this completely eliminates the constants pool entry! (there are specific instructions for pushing the integer values -1 through to +5 onto the stack)[/crazyness]

The same goes for people using Math.PI; cast it to a float before you use it, to avoid the overhead of a huge 8byte! constants pool entry.

Structuring your code so the active scope of a variable is as small as possible will also give you gains.
Due to Java’s smaller-bytecode instructions for accessing the first 4 local variables (1byte rather than 2),
keeping the active set of local variables as small as possible will help your code size.

@Anon666 Loads of stuff I didn’t know there, although I had found that changing the scope of local variables sometimes improved matters & sometimes didn’t, for no obvious reason :slight_smile:

Are you trying to tell me that javac doesn’t evaluate the 3+3 and put a 6 in the bytecode when compiling the source code? I have a hard time believe that, but not enough energy to check :).

oh no, you are absolutely right! javac does evaluate it (and infact expands it to a long constant 6L ).
Thats the problem, and why the optimal solution cannot be achieved by compiling with javac - without later performing such bytecode optimisations with a tool like BCEL.

Heh… I had no idea about that. I’m just not that good a programmer >)

hmmm

i have made some test on that long constants optimizing

Mi early state game with this:

df = (int)((getWidth() / 2) / Math.tan(Math.PI / 4));

jared 3918b

with this
df = (int)((getWidth() / 2) / (float)Math.tan(Math.PI / 4));

3920b

but, it’s not significative, i have been noticing something very strange:

before doing nothing to the code: 3920b
after adding a new blank line 3916b

:-\

the actual size of the bytecode can be affected simply by recompiling??!!

ok more strange things (at least for me)

several line statements like:


double _jy = (getHeight() / 2 
             - ((getHeight() / 2 - (FIELD_Y + FIELD_H)) * df))
             / e.getY() - df;

seems to add a couple of bytes to what this would produce:

double _jy = (getHeight() / 2 - ((getHeight() / 2 - (FIELD_Y + FIELD_H)) * df)) / e.getY() - df;

All these are new things for me, i’m learning a lot with this contest, but i don’t know what are true or what are just the affairs of the pixie that lives inside my pc.

I assume there’s no difference after compiling between the different casting types for simple data types? (float)1 and 1F, for example? Just curious :>

The bit you want to be casting in that expression is the ((float)Math.PI)/2.

When performing micro-optimisations I don’t find it useful comparing the compressed jar size, as you cannot feasably measure the results of your change.

Simply try and reduce your compiled class size by as much as possible, (so the information being stored has reduced - atleast giving the potencial for the jar to get smaller)
while also considering any potencial reduction in the size of the symbol set used in the file. (to aid the compression algorithm)

The only difference that could have on the resultant class file size, is that the debug information would be larger.
You should be compiling with debug off. (though passing the classes through any kind of obfuscator would also remove this information)

With debug off, white space has zero impact of the resultant binary size.

thanks :slight_smile: that clarifies it a lot

Compiling with javac, no, both of those expressions would be resolved to the same bytecode.

However from a bytecode perspective they are different.

Example 1:

b1[/b] occupies 2bytes :-


iconst_1 //push the integer constant 1 onto the stack
i2f            //converts the integer on the top of the stack to a float

1F occupies just 1byte :-

fconst_1 // push the float constant 1.0f onto the stack.

Javac would always evaluate both expressions to the second bytecode sequence - which is optimal.

However - if the value ‘1’ was replaced with the value ‘5’, things become a little different.

Example 2:

b5[/b] still occupies 2 bytes :-


iconst_5 // push the constant 5 onto the stack
i2f            // cast it to a float.

however, 5F occupies a total of 4+2 bytes! :-

constants pool Entry:
[1] = 5.0f (4bytes)

ldc 1// load constant 1 onto the stack

In this instance, Javac would again generate bytecode that matched the second case.
However in this case, it is clearly not size-optimal.

The reason for the difference, is the bytecode instruction set contains 6 single bytecode instructions for pushing the integer constants (-1 through to +5) onto the stack,
where-as it has only 3 single bytecode instructions for pushing float constants (0 through to +2) onto the stack.

Incidentally, this is a good site for a clear overview of the instruction set:

http://homepages.inf.ed.ac.uk/kwxm/JVM/codeByNo.html