Difference between revisions of "Code Size/Old discussion"

From Robowiki
Jump to navigation Jump to search
(added categories)

Revision as of 09:11, 1 May 2009

The tips on this page are specific ways you can rewrite your code to use fewer bytes, as measured by the CodeSize utility. Please note that CodeSize is not affected by the length of your method or variable names.

Discussion is at the bottom of this page.


  • Static variables take less space than non-static ones.
    • Starrynte adds: They save 5 bytes to be exact
  • Don't initialize your number variables to zero - they're zero by default (thanks to the Java Spec)
  • try{ ... } catch is a great way to save space. For example,
            LinkedList list;
            try{
                someFunction(list.getFirst());
            } catch(Exception ex){}
is smaller than:
            LinkedList list;
            if(list.size() != 0){
                someFunction(list.getFirst());
            }
  • Any time you are "doing the same thing" in more than one place, try to find a way to condense it into a method and call it in each place. In some situations, this may seem obvious, but in others it is less obvious. A line from Komarious that makes use of a sign(double d) method where you wouldn't normally expect one:
            _goAngle = angleFromWaveSource +
                directedGoAngle(direction = (sign(checkDanger(-1) - checkDanger(1))));
  • Another somewhat similar situation: there are four places in Komarious that call wave.sourceLocation.distance(someOtherPoint). Creating a distanceToPoint(Point2D) method in the Wave class, and replacing those four calls with wave.distanceToPoint(someOtherPoint) ends up saving 3 bytes; you are getting rid of 4 references to sourceLocation in your code, but adding the size of the method itself.
  • Local variables cost less to reference than static ones, so it may pay to create one just to hold a static variable's value for the duration of a method.
  • I think I saw this somewhere else on the wiki, but:
            int i = 1;
            if (j > 2) {
                i = -1;
            }
is smaller than
            int i;
            if (j > 2) {
                i = -1;
            } else {
                i = 1;
            }
  • Sometimes
            double a;
            double t = (a = e.getBearing()) * getEnergy();
is smaller than
            double a = e.getBearing();
            double t = a * getEnergy();
  • If you can afford the loss of precision, integers are smaller than doubles.
  • String.valueOf(x).concat(" is my number") is WAY smaller than x + " is my number". The compiler turns the latter into something like:
            StringBuilder s = new StringBuilder();
            s.append(x);
            s.append(" is my number");
            s.toString();
  • The integer literals in the range [-1, 5] are one unit (byte?) smaller than the others in the range [-128, 127], which are one byte smaller than the others in the range [-32768, 32767], which are one byte bigger than the other integers.
  • Be careful about promotion in your arithemic. some_int + some_int + some_double (add two integers, promote the result to a double, add two doubles) is smaller than some_int1 + some_double + some_int2 (promote some_int_1 to a double, add two doubles, promote some_int_2 to a double, add to doubles).
  • The double literals 0, 1 are one byte smaller than -1, 2, 3, 4, 5, which are one byte smaller than the rest of the doubles (including those in between the numbers listed).
  • if (some_int == 0) is smaller than if (some_int == 1), but the same size as if (some_boolean).
  • Declaration and initialization of static final fields are free, as long as they don't require any method calls. They are like macros that insert literals into your code.
  • Arithmetic, casting and string concatenation of literals are free.
  • Be careful with the above rule: x + 1 + 2 does not count, you must do x + (1 + 2).
  • You pay for the default constructor of a class even if you don't write the empty code for it yourself, so if you have something useful to put there, go ahead and put it there.
  • If you initialize any static variables (that are not final), you pay one extra byte, so if you aren't saving *any* data between battles, initialize your variables in the run method or in your constructor (which you are paying for whether you want to or not).
  • The first 4 variables in a method represent registers, the rest represent places in memory. I discovered this over the weekend. I now name my first 4 variables r1i (an integer), r2d (a double), r4p (a Point2D.Double) etc. USE THIS FACT. All operations must work from registers, and every load from memory into a register requires an operation, as does storing from a register back to memory. By using your first 4 variables wisely, you can save yourself a lot of load & store operations (which each cost you in CodeSize). Some more notes:
    • The first variable passed to a non-static method is "this". It uses your first register.
    • The next variables declared in your method are the parameters. They use your next registers.
    • All types except double use up 1 register, doubles use 2 (thus the name, "double").
    • You can declare a double as your 4th register (which would overflow to your 5th register), and magically it still works.

Please add to this table as you can, and organize it if you like: (Need to make table from it! Old wiki has slightly different code..)

  • | Operation | Cost in CodeSize
  • | 1st constructor | free
  • | creating a method | free
  • | returning from a method | 1
  • | calling a method | 3
  • | storing a local (non-register) variable | 1
  • | loading a local (non-register) variable | 1
  • | storing a static variable | 2
  • | loading a static variable | 2
  • | loading integer literals -1 to 5 | 1
  • | loading integer literals -128 to 127 | 2
  • | loading integer literals -32768 to 32767 | 3
  • | loading other integer literals | 2
  • | loading double literals 0, 1 | 1
  • | loading double literals -1, 2, 3, 4, 5 | 2
  • | loading other double literals | 3
  • | loading string literals | 2
  • | declaring a variable | free
  • | casting | 1
  • | promotion | 1
  • | arithmetic | 1
  • | ++ and -- (register or not) | 1



Discussion

What about using ternaries? So like in your example: Is

double rawr[] = new double[12];
rawr[2][4] = (rawr[].length > 0) ? 10 : rawr[2][4];

Smaller then

double rawr[] = new double[12];
if(rawr[].length > 0)
    rawr[2][4] = 10;

-- Chase-san

I would guess that they compile to the exact same thing, so there would be no gain there. -- Voidious

It is universally true (in the Java universe) that uninitialized variables are 0, false, or null. -- Simonton

First of all. Cool page, and which is a great inspiration. -- Fnl

I am wondering if:

            int i;
            if (j > 2) {
                i = -1;
            } else {
                i = 1;
            }

Could be written even smaller:

            int i = (j > 2) ? -1 : 1;

At least it should have a faster execution time! ;-) --Fnl

  • Yes, you are right. Smaller still would be int i = (int) Math.signum(2 - j), if you could afford the situation that i comes out to be zero. Or, for one extra byte (still smaller, though), you can almost always afford a rare, rare situation that something like int i = (int) Math.signum(2.00000000000001 - j) would come out to zero. -- Simonton

Hehe..

            LinkedList list;
            try{
                someFunction(list.getFirst());
            } catch(Exception ex){}

..is smaller, but more expensive in execution time due to the try-catch! ;-) --Fnl

A few of my tricks. Remember your binary operators &, |, and especially ^ (xor). Xor is great for swapping very cheaply between 2 integer numbers for next to nothing:

            variable = 64;
...
            variable ^= 32;

is a very cheap way to swap between 64 and 96. Just be careful of the binary math :)

It is almost always cheaper to directly call a method that has 2 or more lines of code than to repeat the code. I've manually called OnDeath(null) before to reinitialize variables and such.

In the spirit of smaller numbers, it is cheaper to divide by 5 than to multiply my 0.2. Same for any other operator.

I know there is another page around here I've posted on before about this - one I started a long time ago. I also know there is a lost thread over at the Repository than had similiar info. I wonder if that could be recovered. --Miked0801

  • I'm interested by your "number swapping" trick. Can you give an example of when this is useful? Thanks! -- Simonton
    • I've used it to multi-mode between 2 movement amounts - say you do better with small movements against some and large against others, xor between the 2 values on OnDeath or whatever. I believe the current Moebius uses this. It also works for swapping between non similiar positive and negative numbers as well. You can imagine where that could be useful.

Well, here's one little trick I found that I thought I'd share. At least with Java 6 compiling in Java 5 compatibility mode (I haven't tested other configurations), the following:

public class Foo {
    public Foo() {}

    public static final Foo getOne() {
        return new Foo();
    }
}

def void doStuff() {
    Foo a = Foo.getOne();
    Foo b = Foo.getOne();
    Foo c = Foo.getOne();
}

is smaller than:

public class Foo {
    public Foo() {}
}

def void doStuff() {
    Foo a = new Foo();
    Foo b = new Foo();
    Foo c = new Foo();
}

In other words it appears that factory methods are smaller than constructors if used 3 or more times. They are the same size if used twice, but are larger if the constructor is only used once. -- Rednaxela

Lol, double bulletPower = 3/2; uses up less space than double bulletPower = 1.5; (2 bytes(?) to be exact) --Starrynte

That would be because 3/2 is integer arithmetic.... essentially equivalent to 1. Try putting 1.0 in there, it should be the same =) -- Skilgannon

In a method,

if(condition){
   foo1();
   return;
}
foo2();

is 2 bytes smaller than

if(condition){
    foo1();
}else{
    foo2();
}

--Starrynte