Showing posts with label yUML. Show all posts
Showing posts with label yUML. Show all posts

Thursday, May 15, 2014

ASTNode Class Hierarchy Phase II and Efficiency Of instanceof Operator In Java


The charts for Phase II are roughly split between two categories. Category A consists of charts in which all distinct entities have been assigned to their own specific classes or interfaces. Charts in category B have classes which subsume other potential classes (similar to how ASTNode is currently structured). All the charts, including their descriptions, can be found below:

Category A
1A: (Fully Extended) All entities which could possibly have their own class or interface have them. No concessions have been made.
2A: (Semi-Fully Extended) All trigonometric functions extend ASTTrigonometricNode. All hyperbolic functions extend ASTHyperbolicNode.
3A: Similar to 2A, but all logical and relational operators (i.e. XOR, OR, ==, >=) extend the same class.

Category B
1B: (Conservative) None of the relational / logical operators have their own classes.
2B: (Ultra Conservative) None of the trigonometric functions have their own class. None of the operators relational / logical operators have their own class.

Generally speaking, the charts in category A tend to be larger than the charts in category B. However, whether or not the charts in category A are cleaner or easier to understand is a matter of personal opinion. If design aesthetics were the only factor to consider then the final decision would ultimately be an arbitrary one. Unfortunately, the consequences of choosing one option over another are not just restricted to offended sensibilities and smarting eyes. Tied to each design is a (suspected) impact on performance. Extending the class hierarchy to its utmost limits, as is done in most charts belonging to category A, results in diagrams with numerous classes wherein relationships are very clearly defined. For these charts it is expected that the instanceof operator will be excessively used (to determine a particular object's class).

The efficiency of the instanceof operator, especially as it relates to the library's use has not been confirmed. Googling the issue yields this excellent stack overflow thread which while enlightening yields many contradictory answers. A clearly defined answer to the question is therefore quite difficult to pin down. In order to have something to work with in the interim, I constructed a test that would enable me to compare the performance of the different methods:

import java.util.concurrent.TimeUnit;

public class PerformanceTest {
  
  private static enum Type {
    A,
    B,
    C,
  }
  
  public static void main(String[] args) {
    long x, y, equals_time = 0, instanceof_time = 0, relative_eq_time = 0, 
         getclass_time = 0, getclass_eq_time = 0, MAX = Long.parseLong(args[0]);
    A a = new A();
    int i, j, k, l, m;
    boolean bool;
    Type type = Type.C;

    // Time comparison using instanceof operator
    for (l = 0; l < MAX; l++) {
      x = System.nanoTime();
      bool = a instanceof A;
      y = System.nanoTime();
      instanceof_time += y - x;
    }

    // Time comparison using relative eq operator
    for (m = 0; m < MAX; m++) {
      x = System.nanoTime();
      bool = type == Type.C;
      y = System.nanoTime();
      relative_eq_time += y - x;
    }

    // Time comparison using .equals() (between the different enums)
    for (k = 0; k < MAX; k++) {
      x = System.nanoTime();
      bool = type.equals(Type.C);
      y = System.nanoTime();
      equals_time += y - x;
    }

    // Time comparison using .getClass().getName() ==
    for (i = 0; i < MAX; i++) {
      x = System.nanoTime();
      bool = a.getClass().getName() == "A";
      y = System.nanoTime();
      getclass_eq_time += y - x;
    }

    // Time comparison using .getClass().getName().equals()
    for (j = 0; j < MAX; j++) {
      x = System.nanoTime();
      bool = a.getClass().getName().equals("A");
      y = System.nanoTime();
      getclass_time += y - x;
    }
    
    String results = String.format(".getClass.getName().equals(\"A\"): %dms\n.getClass().getName() == \"A\": %dms\na.equals(Type.A): %dms\na == Type.A: %dms\na instanceof A: %dms",                               TimeUnit.NANOSECONDS.toMillis(getclass_time), 
                                   TimeUnit.NANOSECONDS.toMillis(getclass_eq_time), 
                                   TimeUnit.NANOSECONDS.toMillis(equals_time),
                                   TimeUnit.NANOSECONDS.toMillis(relative_eq_time),
                                   TimeUnit.NANOSECONDS.toMillis(instanceof_time));
    
    System.out.println(results);
  }
  
}

This is how the times break down grosso modo (they are not always consistent):

.getClass.getName().equals("A"): 87ms
.getClass().getName() == "A": 38ms
a.equals(Type.A): 46ms
a == Type.A: 55ms
a instanceof A: 56ms

Through the above test it is evident that identifying a class' identity with .getClass.getName().equals() exacts the greatest cost to the CPU. Conversely, the least expensive approach is to use the equals method associated with the object's type enum. The performance of the instanceof and == operator sits somewhere in between the two extremes. It should be noted that switching the relative positioning of the different subroutines causes a change in results. For example, reversing the positioning of the functions results in an almost complete reversal of the ranking. This code for instance ...

import java.util.concurrent.TimeUnit;

public class PerformanceTest2 {
  
  private static enum Type {
    A,
    B,
    C,
  }

  private static Type type = Type.C;

  public static void main(String[] args) {
    long x, y, equals_time = 0, instanceof_time = 0, relative_eq_time = 0, 
         getclass_time = 0, getclass_eq_time = 0, MAX = Long.parseLong(args[0]);
    A a = new A();
    int i, j, k, l, m;
    boolean bool;

    // Time comparison using .getClass().getName().equals()
    for (j = 0; j < MAX; j++) {
      x = System.nanoTime();
      bool = a.getClass().getName().equals("A");
      y = System.nanoTime();
      getclass_time += y - x;
    }

    // Time comparison using .getClass().getName() ==
    for (i = 0; i < MAX; i++) {
      x = System.nanoTime();
      bool = a.getClass().getName() == "A";
      y = System.nanoTime();
      getclass_eq_time += y - x;
    }

    // Time comparison using .equals() (between the different enums)
    for (k = 0; k < MAX; k++) {
      x = System.nanoTime();
      bool = type.equals(Type.C);
      y = System.nanoTime();
      equals_time += y - x;
    }

    // Time comparison using .equals() (between the different enums)
    for (k = 0; k < MAX; k++) {
      x = System.nanoTime();
      bool = type.equals(Type.C);
      y = System.nanoTime();
      equals_time += y - x;
    }

    // Time comparison using relative eq operator
    for (m = 0; m < MAX; m++) {
      x = System.nanoTime();
      bool = type == Type.C;
      y = System.nanoTime();
      relative_eq_time += y - x;
    }

    // Time comparison using instanceof operator
    for (l = 0; l < MAX; l++) {
      x = System.nanoTime();
      bool = a instanceof A;
      y = System.nanoTime();
      instanceof_time += y - x;
    }

    String results = String.format(".getClass.getName().equals(\"A\"): %dms\n.getClass().getName() == \"A\": %dms\na.equals(Type.A): %dms\na == Type.A: %dms\na instanceof A: %dms",                               TimeUnit.NANOSECONDS.toMillis(getclass_time), 
                                   TimeUnit.NANOSECONDS.toMillis(getclass_eq_time), 
                                   TimeUnit.NANOSECONDS.toMillis(equals_time),
                                   TimeUnit.NANOSECONDS.toMillis(relative_eq_time),
                                   TimeUnit.NANOSECONDS.toMillis(instanceof_time));
    
    System.out.println(results);
  }
  
}

... yields the following times (on average of course):

.getClass.getName().equals("A"): 47ms
.getClass().getName() == "A": 81ms
a.equals(Type.A): 52ms
a == Type.A: 56ms
a instanceof A: 72ms

Does anybody know what could be causing this?

Overall, these results suggest a variety of things. It is possible that the above approach to assessing efficiency is inherently flawed and that it is unlikely to yield reliable or consistent results. If the test itself is not flawed, then it is likely that performance is being governed by factors that are not under our immediate control. Finally, another explanation for the results could be that at the JVM level the operators take the same amount of time to evaluate and that the variation seen is just what's expected and normal.

While the above tests are inconsistent and not particularly rigorous they at least demonstrate that instanceof's performance is roughly comparable to that of the other operators. Using one operation over another does not result in a significant difference in performance time(on the order of seconds). Thus, we should not allow optimitization to influence our design decisions (in this case Knuth's overused 'premature optimization is the root of all evil' aphorism actually applies). Given that the class hierarchy diagrams in category A respect OOP principles more and have more well defined relationships it is recommended that they be used as a starting point.

Sunday, May 11, 2014

ASTNode Class Hierarchy

Focusing on the fundamental requirements of the ASTNode class and crossing out the non-vital aspects yielded a class hierarchy that looked very much like the one provided by my mentors. My proposed class hierarchy, Mock-up A, can be found here. It is a near faultless reproduction of the original class hierarchy mock-up, with the only difference being that class ASTSymbol directly extends ASTNode instead of ASTNumber & ASTFunction.

Mock-up B is the original mock-up. In it, ASTSymbol is subsumed by both ASTNumber and ASTFunction. The original mock-up did not specify just what this would entail, so I recapitulated the structure of the diagrams.

A concern I had while constructing these diagrams, is that it was not very clear to me where logical operators, relational operators and trigonometric operators fit into the whole scheme. Will some of these need to have their own classes? Any thoughts?

The yUML code used to construct the charts can be found here.