0

I am new to java and I am trying to optimize my code as far as possible.

I want to know what is the most efficient way for the machine to convert a number, lets say int or char, to a boolean.

I am implementing TicTacToe in java. I am using bitmaps to represent the state of the game. I am aware of the unreadability, but I don't want to optimize that. Code efficient < machine efficient.

import java.util.Scanner;

public class TicTacToe {
    boolean swap = false;
    char player1 = 0b000000000;
    char player2 = 0b000000000;
    static char[] winconditions = new char[]{0b111000000, 0b000111000, 0b000000111, 0b100100100, 0b010010010, 0b001001001, 0b100010001, 0b001010100};
    Scanner scanner = new Scanner(System.in);

    boolean evaluateWinner() {
        char current = swap ? player2 : player1;
        for (char condition : winconditions)
            if ((condition == (current & condition))) return true;
        return false;
    }

    int getInput() {
        int field = player1 | player2;
        while (true) {
            System.out.println("Give Input: 0 1 ... 8 ");
            int out = 1 << scanner.nextInt();
            if ((out & field) == 0) return out;
        }
    }

    public void start() {
        do {
            draw();
            if (swap) player2 |= getInput();
            else player1 |= getInput();
            swap = !swap;
        } while (evaluateWinner());
    }

    void draw() {
        for (int i = 0; i < 9; i++) {
            boolean isP1 = (player1 & 1 << i) != 0;
            boolean isAny = (player2 & 1 << i) != 0 || isP1;
            String out = isAny ? (is1 ? "X" : "O") : String.valueOf(i);
            if (i % 3 == 2) out += "|\n";
            System.out.print("|" + out);
        }
    }
}

Call var game = new TicTacToe(); game.start(); to run the game.

In TicTacToe.draw I need to determine if a specific bit is set to 1 or 0. With a few bitwise operations I am getting a number. In C I wouldn't have to convert the number to boolean. But in Java I have to. What is the most efficient way to do that?

  1. n != 0
boolean isP1 = (player1 & 1 << i) != 0;
boolean isAny = (player2 & 1 << i) != 0 || isP1;
  1. (boolean) n
boolean isP1 = (boolean) (player1 & 1 << i) ;
boolean isAny = ((boolean)(player2 & 1 << i))|| isP1;

Is it the same or are there more efficient ways to do convert numbers to a boolean?


If you have more ideas how to optimize the code in cpu runtime and in memory usage. Let me know. I don't think there is much more I can do. But proof me wrong if you want.

Kind reagards : )


Shooper
  • 3
  • 1
  • Why are you using `char`s in the first place? We could use a `boolean[][]`, `boolean[]` or - if we want to stay with the bit-juggling - an `int`. – Turing85 Jun 03 '23 at 13:09
  • 1
    Or a [`BitSet`](https://docs.oracle.com/javase/8/docs/api/java/util/BitSet.html). – Elliott Frisch Jun 03 '23 at 13:17
  • @Turing85 i want to stay with the bit-juggling because I guess its faster to use bitmaps to determine if someone won. int is 32 bit but I only need 9 bits to represent the field state. and char is 16 bit so i have less always 0 bits in my memory But yes, if booleans are stored with 1 bit in jvm, not with 1 byte as in C or on our physical machine, this would be a good optimization in memusage – Shooper Jun 03 '23 at 13:47
  • You do know that, in bytecode, there is no code to load a `char` from local memory, right? Every datatype `< int` (i..e `boolean`, `byte`, `short`, `char`) is handled through `iload`, `istore`, ... (array-types are the exception, there are codes to, e.g., load `boolean`s from an array). – Turing85 Jun 03 '23 at 14:20
  • Besides... if we really want to optimize, we could use a single `int` to encode both player's marks, as well as whose player's turn it is. We would use `19` out of `32` bits. But to be honest, I smell a case of premature optimization, and ["*premature optimization is the root of all evil*" -- Donald Ervin Knuth: *Computer Programming as an Art* (1974), p. 671](https://dl.acm.org/ft_gateway.cfm?id=361612&ftid=289767) – Turing85 Jun 03 '23 at 14:35
  • 1
    @Shooper the HotSpot JVM uses a `byte` per `boolean` for arrays. If you want to use a single bit per `boolean` you have to use `BitSet` or stay with your `char`. – Holger Jun 06 '23 at 13:19

1 Answers1

1

As long as your code lacks correctness, i.e. the condition of while(evaluateWinner()) is wrong and there’s no check for a draw, you should not worry about performance.

Letting aside that in this simple program, performance doesn’t matter at all, you are focusing on the wrong things. You are performing string concatenation in a loop and calling print nine times in a row. The form of the boolean expression is entirely irrelevant. Even performing modulo 3 is more expensive here (unless the JVM’s optimizer replaces it).

If you want to continue, as a fun exercise, consider the following points:

  • Reduce the number of I/O operations (i.e. one print instead of nine)
  • Prefer local variables
  • Use the smaller integer types (byte, short, char) only for arrays. For most operations, especially local variables and arithmetic, they may counter the intended effect: Java actually uses int, but has to perform additional truncation
  • If you need an immutable char[], use String. The encapsulation has no noticeable performance penalty but enables lots of potential optimizations.
  • If your replace player1 & 1 << i with player1 >> i & 1 you get zero or one, then, instead of testing against zero and conditionally using 'X' or 'O', you can use it as an index into "OX", avoiding the conditional.

E.g.

public class TicTacToe {
    static final String WIN_CONDITIONS="\u01c0\070\007\u0124\222\111\u0111\124";

    final Scanner scanner = new Scanner(System.in);
    int player1 = 0b000_000_000, player2 = 0b000_000_000;

    boolean evaluateWinner(int current) {
        for(int ix = 0; ix < WIN_CONDITIONS.length(); ix++) {
            char condition = WIN_CONDITIONS.charAt(ix);
            if((condition == (current & condition))) return true;
        }
        return false;
    }

    int getInput() {
        int field = player1 | player2;
        for(;;) {
            System.out.println("Give Input: 0 1 ... 8 ");
            int out = 1 << scanner.nextInt();
            if((out & field) == 0) return out;
            System.out.println("already occupied");
        }
    }

    public void start() {
      for(boolean swap = false; ; swap = !swap) {
            draw();
            int p = swap? player2: player1;
            p |= getInput();
            if(evaluateWinner(p)) {
                System.out.println(swap? "O wins": "X wins");
                break;
            }
            if(swap) player2 = p; else player1 = p;
            if((player1 | player2) == 0x1FF) {
                System.out.println("Draw");
                break;
            }
        }
    }

    void draw() {
        char[] chars = "|0|1|2|\n|3|4|5|\n|6|7|8|\n".toCharArray();
        int p1 = player1, field = p1 | player2;
        for(int i = 0, j = 1; i < 9; j += 2)
            for(int c = 0; c < 3; c++, i++, j+=2)
              if((field & 1 << i) != 0) chars[j] = "OX".charAt(p1 >> i & 1);
        System.out.print(chars);
    }
}

As said, you won’t notice actual performance differences with such a simple program, but it’s a fun exercise providing some ideas.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    One thing to suggest, even when done as a fun exercise is to always *test* performance before and after "optimizing". This might involve reading up and learning about [how to microbenchmark in Java](https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java). Otherwise there's a risk they are learning and reinforcing some wrong assumptions. – Joachim Sauer Jun 06 '23 at 16:59
  • 1
    @JoachimSauer Even correct assumptions should not be memorized as eternal truths, as they may become wrong in the future. It’s also worth noting that a variant winning in a microbenchmark may still perform worse in the context of a specific application. This specific program will be dominated by the time waiting for the user, so it’s possible that the JVM’s optimizer does not do much with this code, as it doesn’t pay off. So the code may perform differently than in a benchmark loop. – Holger Jun 06 '23 at 17:08