4
\$\begingroup\$

I've been learning Processing for the last week or two, and I've made a couple of little 'desktop toy' type apps like this one that I'm quite pleased with. However, it just occurred to me today to look at the activity monitor while my app was running, and I was unpleasantly surprised – nobody wants a desktop toy that's consuming more resources than anything else on the system.

So far throughout my coding education/experience I've been told not to worry about optimisation 'yet', and haven't really been offered much guidance re: efficient coding (faculty are mainly interested in code that's easy to mark).

Here's what the app is presenting to the user:

yijing clock screenshot

Versus the resources it's consuming:

enter image description here

The source is in two parts: the main .pde and another containing the 'Gua' (hexagram) class.

yijingClock.pde

PGraphics mins;
PGraphics hrs;
float fadeAmount;
static final float fadeMax = 1440; //1440 means 1 step per frame takes 1 minute at 24fps

void setup() {
  size(500, 500);
  colorMode(RGB, 255, 255, 255, fadeMax);
  background(255);
  imageMode(CENTER); // All images in the sketch are drawn CENTER-wise
  frameRate(24); // Affects the smoothness and speed of fade();
  mins = createGraphics(width, height);
  hrs = createGraphics(width, height);
  noFill();
  stroke(100);
  //polygon(5, width/2, height/2, width / 4 * 3, height / 4 * 3, -PI / 2); // Draw a static pentagon as the background
  fill(0);
  fadeAmount = 0;
} // end setup()


void draw() {
  //let's fade instead of redrawing the background to expose change over time
  fadeAmount = map(System.currentTimeMillis() % 60000, 0, 60000, 1, fadeMax); // new way explicitly ties the fade amount to the real current second
  //println(fadeAmount);
  fade(fadeAmount);

  drawMins();
  drawHrs();
}// end draw()


void drawMins() {
  Gua gua = new Gua(minute());
  mins = gua.drawGua(color(0, 0, 0, constrain(fadeAmount*2, 100, fadeMax)));
  image(mins, width/2.0, height/2.0, width/2.5, height/2.5);
}// end drawMins()


void drawHrs() {
  float angle = TWO_PI / 5; // To arrange them in a pentagon
  float startAngle = -PI / 2; // To put the first point at the top
  String binHr = binary(hour(), 5); // Use modulo 12 if we want 12 hour time, but that's a waste of bits imo
  char[] binHrArr = reverse(binHr.toCharArray()); // We're reversing this to match the endianness of the yijing, and the arrangement of a clock.
  hrs = createGraphics(width, height); // Clearing the previous state
  hrs.beginDraw();
  hrs.clear(); // Trying to make the background transparent so we can layer things
  hrs.imageMode(CENTER);

  for (int i = 4; i >= 0; i--) {
    PGraphics bit = hourBit(binHrArr[i]);
    hrs.image(bit, width/2 + (width/2.1) * cos(startAngle + angle * i),
      height/2 + (height/2.1) * sin(startAngle + angle * i),
      25, 25);
  } // end for
  hrs.endDraw();
  image(hrs, width/2, height/2, (width/4)*3, (height/4)*3);
}// end drawHrs()


// Returns a full circle for true and a hollow one for false
PGraphics hourBit(char state) {
  PGraphics bit = createGraphics(40, 40);
  bit.beginDraw(); // Start drawing to this buffer...
  bit.imageMode(CENTER);
  //bit.clear();
  bit.stroke(0);
  // Fill colour based on state, 1 = filled.
  if (state == '1') {
    bit.fill(0, fadeAmount); // They fade in
  } else {
    bit.fill(255);
  } // end if
  bit.ellipse(20, 20, 30, 30);
  bit.endDraw();
  return bit;
} // end hourBit()


/**
 Copypasted from https://processing.org/tutorials/anatomy/
 Originally i was drawing a pentagon but mainly i just needed to learn
 how to get the coordinates of the points.
 **/
void polygon(int n, float cx, float cy, float w, float h, float startAngle) {
  float angle = TWO_PI/ n;
  // The horizontal "radius" is one half the width,
  // the vertical "radius" is one half the height
  w = w / 2.0;
  h = h / 2.0;
  beginShape();
  for (int i = 0; i < n; i++) {
    vertex(cx + w * cos(startAngle + angle * i),
      cy + h * sin(startAngle + angle * i));
  }
  endShape(CLOSE);
}

/**
 Draws a semitransparent background, which fades everything on
 screen by a given amount
 **/
void fade(float amount) {
  fill(255, amount); // hardcoding bc 1. im lazy, and 2. this sketch should always be b/w
  rectMode(CENTER);
  rect(width/2, height/2, width, height);
}// end fade

Gua.pde

class Gua {
  int[] yao = new int[6];
  PImage img;
  int intValue = 0; // Used to cast the binary to base 10 to represent the hexagram that way
  static final int height = 300; // Need a good alternative to using these constants here... 
  static final int width = 300;
  static final int lineHeight = height / 12; // the height of a line; voids are equal height to lines
  static final int segmentSize = width / 5; // Used to determine the ratio of line to white in yin line; must be an odd number to allow middle to be empty

  private int x = 0; // remember that (0,0) is the top left corner. Not using x really.
  private int y = 0; // Used when moving down the hexagram while drawing it



  /** 
  Default constructor instantiates a random hexagram. This hexagram exists in the memory,
  and must be drawn to the canvas in a seperate operation. 
  **/
  Gua() {
    for (int i = 0; i < 6; i++) {
      yao[i] = round(random(0, 1));
      //print(yao[i]);
    }
    //println();
  } // end Gua()

  // Construct a hexagram with a given (base 10) value.
  Gua(int value) {
    String binary = binary(value, 6);
    //println("parsed binary: " + binary);
    for (int i = 0; i < binary.length(); i++) {
      yao[i] = Character.getNumericValue(binary.charAt(i));
      //print(yao[i]);
    }    
    //println();
  } // end Gua(int)

  // Convenience version comes in black
  PGraphics drawGua() {
    return drawGua(color(0,0,0));
  } // end drawGua()
/** Draw a gua of a given colour **/
  PGraphics drawGua(color col) {

    PGraphics pg = createGraphics(width, height); // the image buffer we will return
    pg.beginDraw();
    //pg.clear();
    pg.noStroke();
    pg.fill(col);
    //pg.background(255, alpha(col));
    int i = 5; // we have to read them backwards because tradition places the "smallest bit" at the top

    for (y = 0; y <= 250; y = y + 50) {
      if (yao[i] == 0) { // yin        
        pg.rect(x, y, (segmentSize * 2), lineHeight); // left side
        pg.rect(segmentSize * 3, y, (segmentSize * 2), lineHeight); // right side
        //println("-- --");
      } // end if
      else { // yang
        pg.rect(x, y, width, lineHeight);
        //println("-----");
      } // end else
      i--;
    } //end for
    pg.endDraw();
    return pg;
  }// end drawGua(colour)


  String toString() {
    return join(str(yao), "");
  }// end toString()

  int toInt() {
    return unbinary(toString());
  }// end toInt()
} // End Gua

I probably don't need to point out that I'm a total beginner, so please, even the most obvious optimisation tips would be appreciated. If the answer is that I've written everything totally wrong and should start over, honestly that would be even better, as I feel I need to start developing good habits ASAP.

\$\endgroup\$
  • 1
    \$\begingroup\$ I ran your code on my laptop with the Resource Monitor open and my CPU usage jumped from about 10% to about 60%. Looking at your code, I see one major leak: you re-instantiate a new Gua object every frame. I would recommend creating an "updateTime" method in the Gua class so you only need 2 instances (or create an object that holds hours and minutes so you can have just one). Then simply update the time each frame and draw. \$\endgroup\$ – Eric Roch Dec 13 '15 at 19:52
2
\$\begingroup\$

Something you need to learn about performance in java is that if performance is important to you, Java might not be the language for you. Your code is not directly translated into machine code; the compiler interprets your code, rewrites it with it's own optimization algorithms before translating it to machine code. Of course you can make minor changes to your code to make slight performance improvements, but on a general scale going for performance means it becomes more of a game of "how to trick the compiler" rather than pure performance increase through code.

To your actual code

Comments

You're commenting way too much. If this is for your own learning process and noone else have to look at the code, that's fine, but if this is code you are gonna share, commenting near every line is only gonna make it hard to read quite annoying. Anyone who didn't pick up coding 2 weeks ago don't need to see // end methodName(). You should only write comments when your code does something that's not obvious just by reading it. You're not making a tutorial for people who can't read code, you're writing comments to clarify difficult parts to yourself and your colleagues.

JavaDoc are actually quite useful if you do them right;

/**
 * This method connects to the database and persists the ObjectType entity provided.
 * @param ObjectType
 * @return ObjectType (persisted object)
 */

If you're using an IDE like NetBeans or IntelliJ this JavaDoc will pop up and let you know what the method does and how to use it (just like it will help anyone else using your code). However, you're not writing JavaDoc, you're writing block comments that explains the code, not documents it.

You're also leaving a lot of scrap comments in your code. If you have to comment it out and it's no longer a WIP, remove unused code.

Access modifiers

It's also bad practice to not add access control modifiers, if it's something you haven't studied yet you can read about it in the oracle tutorial.

Magic numbers and hard-coding

You should also try to not hard-code everything. There's something called "magic numbers" and you're using them near everywhere. Ins this for loop you have three:

 for (y = 0; y <= 250; y = y + 50) {}

The first one can be somewhat forgiven since index begins with 0 and there might not be any places you want to modify this, but the other two;

  • What is 250? Where does it come from? Why is it that specific number?
  • y = y + 50 raises the same questions.

Why it's important to not use magic numbers is that a) You'll know what the number represents and it's easier to follow what you're iterating. b) The most important thing! If you need to change these numbers, you only change one variable declared at the top in the global scope, otherwise you need to somehow know and remember which magic numbers are related to this variable and has to be changed as well.

And when you hard-code all the variables locally, your methods are not dynamic and can't be re-used. It might not seem to matter here, but it's a good practice to put in among your habits when coding.

\$\endgroup\$
  • \$\begingroup\$ In any other language I would normally use access control, but some things don't seem to work the way I'd expect in processing? I need to learn more about how it's behaving under the hood and work out when and where and how to use it, the lassaiz-faire vibes in processing took the edge off my core OO concepts I think... \$\endgroup\$ – Toadfish Nov 1 '15 at 12:00
  • \$\begingroup\$ Oh and sorry, but one other question – you say not to expect too much from Java performance wise, but is the CPU/memory utilisation I observed normal for an app this simple? I have no frame of reference, nearly everything I've done so far outside of mobile has been command line applications and they've always been too fast to bother benchmarking. \$\endgroup\$ – Toadfish Nov 1 '15 at 12:02
  • \$\begingroup\$ I'm not familiar with using the lib you're using for your graphics; I use JavaFX for my desktop applications. \$\endgroup\$ – Gemtastic Nov 1 '15 at 12:17
  • 1
    \$\begingroup\$ I would say your CPU usage is too high. My similar Processing project posted here produces only a 10% increase (20% when the analog view is calculating angles). I think a lot of it has to do with how often you are recalculating the same values. You could potentially even add a test to only call the draw methods when the time changes. \$\endgroup\$ – Eric Roch Dec 13 '15 at 19:59
  • 1
    \$\begingroup\$ -1 from the first paragraph. Java won't be as fast as C if programmed perfectly, but it should always be within a factor of 2 or so. That is not usually the problem you will be facing. \$\endgroup\$ – Oscar Smith Feb 3 '18 at 18:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.