The Artima Developer Community
Sponsored Link

Weblogs Forum
The Final Performance Testing Example

11 replies on 1 page. Most recent reply: Aug 24, 2005 3:44 PM by Michael Stover

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 11 replies on 1 page
Bruce Eckel

Posts: 875
Nickname: beckel
Registered: Jun, 2003

The Final Performance Testing Example (View in Weblogs)
Posted: Aug 5, 2005 6:57 PM
Reply to this message Reply
Summary
I've been working on this since the first post of this example; you can see that the code has been completely rewritten and (I think) greatly improved, both in terms of the generics and the performance tests themselves.
Advertisement
A performance test framework
This is why it takes me so long to write (or rewrite) a book. Examples like this, which I think are done, rear up and gobble lots of time. The good thing is that I end up understanding all the problems in much more depth. But it makes books -- especially one the size of Thinking in Java fourth edition -- take forever. The following was taken directly from the book:

A performance test framework

To prevent code duplication and to provide consistency among tests, I’ve put the basic functionality of the test process into a framework. The following code establishes a base class from which you create a list of anonymous inner classes, one for each different test. Each of these inner classes is called as part of the testing process. This approach allows you to easily add and remove new kinds of tests.

This is another example of the Template Method design pattern. Although you follow the typical approach of overriding the template method Test.test( ) for each particular test, in this case the core code (that doesn’t change) is in a separate Tester class.[1] The type of container under test is the generic parameter C:


//: containers/Test.java
// Framework for performing timed tests of containers.

public abstract class Test<C> {
  String name;
  public Test(String name) { this.name = name; }
  // Override this Template Method for different tests.
  // Returns actual number of repetitions of test.
  abstract int test(C container, TestParam tp);
} ///:~

 

Each Test object stores the name of that test. When you call the test() method, it must be given the container to be tested along with a “messenger” or “data transfer object” that holds the various parameters for that particular test. The parameters include size, indicating the number of elements in the container, and loops, which control the number of iterations for that test. These parameters may or may not be used in every test.

Each container will undergo a sequence of calls to test(), each with a different TestParam, so TestParam also contains static array() methods that make it easy to create arrays of TestParam objects. The first version of array() takes a variable argument list containing alternating size and loops values, and the second version takes the same kind of list except that the values are inside Strings—this way it can be used to parse command-line arguments:


//: containers/TestParam.java
// A "data transfer object."
import java.util.*;

public class TestParam {
  public final int size;
  public final int loops;
  public TestParam(int size, int loops) {
    this.size = size;
    this.loops = loops;
  }
  // Create an array of TestParam from a varargs sequence:
  public static TestParam[] array(int... values) {
    int size = values.length/2;
    TestParam[] result = new TestParam[size];
    int n = 0;
    for(int i = 0; i < size; i++)
      result[i] = new TestParam(values[n++], values[n++]);
    return result;
  }
  // Convert a String array to a TestParam array:
  public static TestParam[] array(String[] values) {
    int[] vals = new int[values.length];
    for(int i = 0; i < vals.length; i++)
      vals[i] = Integer.decode(values[i]);
    return array(vals);
  }
} ///:~

 

To use the framework, you pass the container to be tested along with a List of Test objects to a Tester.run() method (these are overloaded generic convenience methods which reduce the amount of typing necessary to use them). Tester.run() calls the appropriate overloaded constructor, then calls timedTest(), which executes each test in the list for that container. timedTest() repeats each test for each of the TestParam objects in paramList. Because paramList is initialized from the static defaultParams array, you can change the paramList for all tests by reassigning defaultParams, or you can change the paramList for one test by passing in a custom paramList for that test:


//: containers/Tester.java
// Applies Test objects to lists of different containers.
import java.util.*;

public class Tester<C> {
  public static int fieldWidth = 8;
  public static TestParam[] defaultParams= TestParam.array(
    10, 5000, 100, 5000, 1000, 5000, 10000, 500);
  // Override this to modify pre-test initialization:
  protected C initialize(int size) { return container; }
  protected C container;
  private String headline = "";
  private List<Test<<C>> tests;
  private static String stringField() {
    return "%" + fieldWidth + "s";
  }
  private static String numberField() {
    return "%" + fieldWidth + "d";
  }
  private static int sizeWidth = 5;
  private static String sizeField = "%" + sizeWidth + "s";
  private TestParam[] paramList = defaultParams;
  public Tester(C container, List<Test<<C>> tests) {
    this.container = container;
    this.tests = tests;
    if(container != null)
      headline = container.getClass().getSimpleName();
  }
  public Tester(C container, List<Test<<C>> tests,
      TestParam[] paramList) {
    this(container, tests);
    this.paramList = paramList;
  }
  public void setHeadline(String newHeadline) {
    headline = newHeadline;
  }
  // Generic methods for convenience :
  public static <C> void run(C cntnr, List<Test<<C>> tests){
    new Tester<C>(cntnr, tests).timedTest();
  }
  public static <C> void run(C cntnr,
      List<Test<<C>> tests, TestParam[] paramList) {
    new Tester<C>(cntnr, tests, paramList).timedTest();
  }
  private void displayHeader() {
    // Calculate width and pad with '-':
    int width = fieldWidth * tests.size() + sizeWidth;
    int dashLength = width - headline.length() - 1;
    StringBuilder head = new StringBuilder(width);
    for(int i = 0; i < dashLength/2; i++)
      head.append('-');
    head.append(' ');
    head.append(headline);
    head.append(' ');
    for(int i = 0; i < dashLength/2; i++)
      head.append('-');
    System.out.println(head);
    // Print column headers:
    System.out.format(sizeField, "size");
    for(Test test : tests)
      System.out.format(stringField(), test.name);
    System.out.println();
  }
  // Run the tests for this container:
  public void timedTest() {
    displayHeader();
    for(TestParam param : paramList) {
      System.out.format(sizeField, param.size);
      for(Test<C> test : tests) {
        C kontainer = initialize(param.size);
        long start = System.nanoTime();
        // Call the template method:
        int reps = test.test(kontainer, param);
        long duration = System.nanoTime() - start;
        long timePerRep = duration / reps; // Nanoseconds
        System.out.format(numberField(), timePerRep);
      }
      System.out.println();
    }
  }
} ///:~

 

The stringField() and numberField() methods produce formatting strings for outputting the results. The standard width for formatting can be changed by modifying the static fieldWidth value. The displayHeader() method formats and prints the header information for each test.

If you need to perform special initialization, override the initialize( ) method. This produces an initialized container object of the appropriate size—you can either modify the existing container object or create a new one. You can see in test( ) that the result is captured in a local reference called kontainer, which allows you to replace the stored member container with a completely different initialized container.

The return value of each Test.test() method must be the number of operations performed by that test, which is used to calculate the number of nanoseconds required for each operation. You should be aware that System.nanoTime() typically produces values with a granularity that is greater than one (and this granularity will vary with machines and operating systems), and this will produce a certain amount of rattle in the results.

The results may vary from machine to machine; these tests are only intended to compare the performance of the different containers.

Choosing between Lists

Here is a performance test for the most essential of the List operations. For comparison, it also shows the most important Queue operations. Two separate lists of tests are created for testing each class of container. In this case, Queue operations only apply to LinkedLists.


//: containers/ListPerformance.java
// Demonstrates performance differences in Lists.
// {Args: 100 500} Small to keep build testing short
import java.util.*;
import net.mindview.util.*;

public class ListPerformance {
  static Random rand = new Random();
  static int reps = 1000;
  static List<Test<List<Integer>>> tests = 
    new ArrayList<Test<List<Integer>>>();
  static List<Test<LinkedList<Integer>>> qTests = 
    new ArrayList<Test<LinkedList<Integer>>>();
  static {
    tests.add(new Test<List<Integer>>("add") {
      int test(List<Integer> list, TestParam tp) {
        int loops = tp.loops;
        int listSize = tp.size;
        for(int i = 0; i < loops; i++) {
          list.clear();
          for(int j = 0; j < listSize; j++)
            list.add(j);
        }
        return loops * listSize;
      }
    });
    tests.add(new Test<List<Integer>>("get") {
      int test(List<Integer> list, TestParam tp) {
        int loops = tp.loops * reps;
        int listSize = list.size();
        for(int i = 0; i < loops; i++)
          list.get(rand.nextInt(listSize));
        return loops;
      }
    });
    tests.add(new Test<List<Integer>>("set") {
      int test(List<Integer> list, TestParam tp) {
        int loops = tp.loops * reps;
        int listSize = list.size();
        for(int i = 0; i < loops; i++)
          list.set(rand.nextInt(listSize), 47);
        return loops;
      }
    });
    tests.add(new Test<List<Integer>>("iteradd") {
      int test(List<Integer> list, TestParam tp) {
        final int LOOPS = 1000000;
        int half = list.size() / 2;
        ListIterator<Integer> it = list.listIterator(half);
        for(int i = 0; i < LOOPS; i++)
          it.add(47);
        return LOOPS;
      }
    });
    tests.add(new Test<List<Integer>>("insert") {
      int test(List<Integer> list, TestParam tp) {
        int loops = tp.loops;
        for(int i = 0; i < loops; i++)
          list.add(5, 47); // Minimize random access cost
        return loops;
      }
    });
    tests.add(new Test<List<Integer>>("remove") {
      int test(List<Integer> list, TestParam tp) {
        int loops = tp.loops;
        int size = tp.size;
        for(int i = 0; i < loops; i++) {
          list.clear();
          list.addAll(new CountingIntegerList(size));
          while(list.size() > 5)
            list.remove(5); // Minimize random access cost
        }
        return loops * size;
      }
    });
    // Tests for queue behavior:
    qTests.add(new Test<LinkedList<Integer>>("addFirst") {
      int test(LinkedList<Integer> list, TestParam tp) {
        int loops = tp.loops;
        int size = tp.size;
        for(int i = 0; i < loops; i++) {
          list.clear();
          for(int j = 0; j < size; j++)
            list.addFirst(47);
        }
        return loops * size;
      }
    });
    qTests.add(new Test<LinkedList<Integer>>("addLast") {
      int test(LinkedList<Integer> list, TestParam tp) {
        int loops = tp.loops;
        int size = tp.size;
        for(int i = 0; i < loops; i++) {
          list.clear();
          for(int j = 0; j < size; j++)
            list.addLast(47);
        }
        return loops * size;
      }
    });
    qTests.add(
      new Test<LinkedList<Integer>>("rmFirst") {
        int test(LinkedList<Integer> list, TestParam tp) {
          int loops = tp.loops;
          int size = tp.size;
          for(int i = 0; i < loops; i++) {
            list.clear();
            list.addAll(new CountingIntegerList(size));
            while(list.size() > 0)
              list.removeFirst();
          }
          return loops * size;
        }
      });
    qTests.add(new Test<LinkedList<Integer>>("rmLast") {
      int test(LinkedList<Integer> list, TestParam tp) {
        int loops = tp.loops;
        int size = tp.size;
        for(int i = 0; i < loops; i++) {
          list.clear();
          list.addAll(new CountingIntegerList(size));
          while(list.size() > 0)
            list.removeLast();
        }
        return loops * size;
      }
    });
  }
  static class ListTester extends Tester<List<Integer>> {
    public ListTester(List<Integer> container, 
        List<Test<List<Integer>>> tests) {
      super(container, tests);
    }
    // Fill to the appropriate size before each test:
    @Override protected List<Integer> initialize(int size){
      container.clear();
      container.addAll(new CountingIntegerList(size));
      return container;
    }
    // Convenience method:
    public static void run(List<Integer> list, 
        List<Test<List<Integer>>> tests) {
      new ListTester(list, tests).timedTest();
    }
  }
  public static void main(String[] args) {
    if(args.length > 0)
      Tester.defaultParams = TestParam.array(args);
    // Can only do these two tests on an array:
    Tester<List<Integer>> arrayTest =
      new Tester<List<Integer>>(null, tests.subList(1, 3)){
        // This will be called before each test. It
        // produces a non-resizeable array-backed list:
        @Override protected 
        List<Integer> initialize(int size) {
          Integer[] ia = Generated.array(Integer.class,
            new CountingGenerator.Integer(), size);
          return Arrays.asList(ia);
        }
      };
    arrayTest.setHeadline("Array as List");
    arrayTest.timedTest();
    Tester.defaultParams= TestParam.array(
      10, 5000, 100, 5000, 1000, 1000, 10000, 200);
    ListTester.run(new ArrayList<Integer>(), tests);
    ListTester.run(new LinkedList<Integer>(), tests);
    ListTester.run(new Vector<Integer>(), tests);
    Tester.fieldWidth = 12;
    Tester<LinkedList<Integer>> qTest = 
      new Tester<LinkedList<Integer>>(
        new LinkedList<Integer>(), qTests);
    qTest.setHeadline("Queue tests");
    qTest.timedTest();
  }
} /* Output: (Sample)
--- Array as List ---
 size     get     set
   10     130     183
  100     130     164
 1000     129     165
10000     129     165
--------------------- ArrayList ---------------------
 size     add     get     set iteradd  insert  remove
   10     121     139     191     435    3952     446
  100      72     141     191     247    3934     296
 1000      98     141     194     839    2202     923
10000     122     144     190    6880   14042    7333
--------------------- LinkedList ---------------------
 size     add     get     set iteradd  insert  remove
   10     182     164     198     658     366     262
  100     106     202     230     457     108     201
 1000     133    1289    1353     430     136     239
10000     172   13648   13187     435     255     239
----------------------- Vector -----------------------
 size     add     get     set iteradd  insert  remove
   10     129     145     187     290    3635     253
  100      72     144     190     263    3691     292
 1000      99     145     193     846    2162     927
10000     108     145     186    6871   14730    7135
-------------------- Queue tests --------------------
 size    addFirst     addLast     rmFirst      rmLast
   10         199         163         251         253
  100          98          92         180         179
 1000          99          93         216         212
10000         111         109         262         384
*///:~

 

Each test requires careful thought to ensure that you are producing meaningful results. For example, the “add” test clears the List and then refills it to the specified list size. The call to clear() is thus part of the test, and may have an impact on the time, especially for small tests. Although the results here seem fairly reasonable, you could imagine rewriting the test framework so that there is a call to a preparation method (which would, in this case, include the clear() call) outside of the timing loop.

Note that for each test, you must accurately calculate the number of operations that occur and return that value from test(), so the timing is correct.

The “get” and “set” tests both use the random number generator to perform random accesses to the List. In the output, you can see that, for a List backed by an array and for an ArrayList, these accesses are fast and very consistent regardless of the list size, whereas for a LinkedList the access times grow very significantly for larger lists. Clearly, linked lists are not a good choice if you will be performing many random accesses.

The “iteradd” test uses an iterator in the middle of the list to insert new elements. For an ArrayList this gets expensive as the list gets bigger, but for a LinkedList it is relatively cheap, and constant regardless of size. This makes sense because an ArrayList must create space and copy all its references forward during an insertion. This becomes expensive as the ArrayList gets bigger. A LinkedList only needs to link in a new element, and doesn’t have to modify the rest of the list, so you expect the cost to be roughly the same regardless of the list size.

The “insert” and “remove” tests both use location number 5 as the point of insertion or removal, rather than either end of the List. A LinkedList treats the end points of the List specially—this improves the speed when using a LinkedList as a Queue. However, if you add or remove elements in the middle of the list, you include the cost of random access, which we’ve already seen varies with the different List implementations. By performing the insertions and removals at location five, the cost of the random access should be negligible and we should see only the cost of insertion and removal, but we will not see any specialized optimization for the end of a LinkedList. You can see from the output that the cost of insertion and removal in a LinkedList is quite cheap and doesn’t vary with the list size, but with an ArrayList, insertions especially are very expensive, and the cost increases with list size.

From the Queue tests, you can see how quickly a LinkedList can insert and remove elements from the endpoints of the list, which is optimal for Queue behavior.

Normally, you can just call Tester.run(), passing the container and the tests list. Here, however, we must override the initialize() method so that the List is cleared and refilled before each test—otherwise the List control over the size of the List would be lost during the various tests. ListTester inherits from Tester and performs this initialization using CountingIntegerList. The run() convenience method is also overridden.

We’d also like to compare array access to container access (primarily against ArrayList). In the first test in main(), a special Test object is created using an anonymous inner class. The initialize() method is overridden to create a new object each time it is called (ignoring the stored container object, so null is the container argument for this Tester constructor). The new object is created using Generated.array( ) (which was defined in the Arrays chapter) and Arrays.asList(). Only two of the tests can be performed in this case, because you cannot insert or remove elements when using a List backed by an array, so the List.subList() method is used to select the desired tests from the tests list.

For random-access get( ) and set( ) operations, a List backed by an array is slightly faster than an ArrayList, but the same operations are dramatically more expensive for a LinkedList because it is not designed for random-access operations.

Vector should be avoided; it’s only in the library for legacy code support (the only reason it works in this program is because it was adapted to be a List for forward compatibility).

The best approach is probably to choose an ArrayList as your default and to change to a LinkedList if you need its extra functionality or you discover performance problems due to many insertions and removals from the middle of the list. If you are working with a fixed-sized group of elements, either use a List backed by an array (as produced by Arrays.asList( )), or if necessary, an actual array.

CopyOnWriteArrayList is a special implementation of List used in concurrent programming, and will be discussed in the Concurrency chapter.

Choosing between Sets

Depending on the behavior you desire, you can choose a TreeSet, a HashSet, or a LinkedHashSet. The following test program gives an indication of the performance trade-off between these implementations:


//: containers/SetPerformance.java
// Demonstrates performance differences in Sets.
// {Args: 100 5000} Small to keep build testing short
import java.util.*;

public class SetPerformance {
  static List<Test<Set<Integer>>> tests =
    new ArrayList<Test<Set<Integer>>>();
  static {
    tests.add(new Test<Set<Integer>>("add") {
      int test(Set<Integer> set, TestParam tp) {
        int loops = tp.loops;
        int size = tp.size;
        for(int i = 0; i < loops; i++) {
          set.clear();
          for(int j = 0; j < size; j++)
            set.add(j);
        }
        return loops * size;
      }
    });
    tests.add(new Test<Set<Integer>>("contains") {
      int test(Set<Integer> set, TestParam tp) {
        int loops = tp.loops;
        int span = tp.size * 2;
        for(int i = 0; i < loops; i++)
          for(int j = 0; j < span; j++)
            set.contains(j);
        return loops * span;
      }
    });
    tests.add(new Test<Set<Integer>>("iterate") {
      int test(Set<Integer> set, TestParam tp) {
        int loops = tp.loops * 10;
        for(int i = 0; i < loops; i++) {
          Iterator<Integer> it = set.iterator();
          while(it.hasNext())
            it.next();
        }
        return loops * set.size();
      }
    });
  }
  public static void main(String[] args) {
    if(args.length > 0)
      Tester.defaultParams = TestParam.array(args);
    Tester.fieldWidth = 10;
    Tester.run(new TreeSet<Integer>(), tests);
    Tester.run(new HashSet<Integer>(), tests);
    Tester.run(new LinkedHashSet<Integer>(), tests);
  }
} /* Output: (Sample)
------------- TreeSet -------------
 size       add  contains   iterate
   10       746       173        89
  100       501       264        68
 1000       714       410        69
10000      1975       552        69
------------- HashSet -------------
 size       add  contains   iterate
   10       308        91        94
  100       178        75        73
 1000       216       110        72
10000       711       215       100
---------- LinkedHashSet ----------
 size       add  contains   iterate
   10       350        65        83
  100       270        74        55
 1000       303       111        54
10000      1615       256        58
*///:~

 

The performance of HashSet is generally superior to TreeSet, but especially when adding elements and looking them up, which are the two most important operations. TreeSet exists because it maintains its elements in sorted order, so you use it only when you need a sorted Set. Because of the internal structure necessary to support sorting and because iteration is something you’re more likely to do, iteration is usually faster with a TreeSet than a HashSet.

Note that LinkedHashSet is more expensive for insertions than HashSet; this is because of the extra cost of maintaining the linked list along with the hashed container.

Choosing between Maps

This program gives an indication of the trade-off between Map implementations:


//: containers/MapPerformance.java
// Demonstrates performance differences in Maps.
// {Args: 100 5000} Small to keep build testing short
import java.util.*;

public class MapPerformance {
  static List<Test<Map<Integer,Integer>>> tests =
    new ArrayList<Test<Map<Integer,Integer>>>();
  static {
    tests.add(new Test<Map<Integer,Integer>>("put") {
      int test(Map<Integer,Integer> map, TestParam tp) {
        int loops = tp.loops;
        int size = tp.size;
        for(int i = 0; i < loops; i++) {
          map.clear();
          for(int j = 0; j < size; j++)
            map.put(j, j);
        }
        return loops * size;
      }
    });
    tests.add(new Test<Map<Integer,Integer>>("get") {
      int test(Map<Integer,Integer> map, TestParam tp) {
        int loops = tp.loops;
        int span = tp.size * 2;
        for(int i = 0; i < loops; i++)
          for(int j = 0; j < span; j++)
            map.get(j);
        return loops * span;
      }
    });
    tests.add(new Test<Map<Integer,Integer>>("iterate") {
      int test(Map<Integer,Integer> map, TestParam tp) {
        int loops = tp.loops * 10;
        for(int i = 0; i < loops; i ++) {
          Iterator it = map.entrySet().iterator();
          while(it.hasNext())
            it.next();
        }
        return loops * map.size();
      }
    });
  }
  public static void main(String[] args) {
    if(args.length > 0)
      Tester.defaultParams = TestParam.array(args);
    Tester.run(new TreeMap<Integer,Integer>(), tests);
    Tester.run(new HashMap<Integer,Integer>(), tests);
    Tester.run(new LinkedHashMap<Integer,Integer>(),tests);
    Tester.run(
      new IdentityHashMap<Integer,Integer>(), tests);
    Tester.run(new WeakHashMap<Integer,Integer>(), tests);
    Tester.run(new Hashtable<Integer,Integer>(), tests);
  }
} /* Output: (Sample)
---------- TreeMap ----------
 size     put     get iterate
   10     748     168     100
  100     506     264      76
 1000     771     450      78
10000    2962     561      83
---------- HashMap ----------
 size     put     get iterate
   10     281      76      93
  100     179      70      73
 1000     267     102      72
10000    1305     265      97
------- LinkedHashMap -------
 size     put     get iterate
   10     354     100      72
  100     273      89      50
 1000     385     222      56
10000    2787     341      56
------ IdentityHashMap ------
 size     put     get iterate
   10     290     144     101
  100     204     287     132
 1000     508     336      77
10000     767     266      56
-------- WeakHashMap --------
 size     put     get iterate
   10     484     146     151
  100     292     126     117
 1000     411     136     152
10000    2165     138     555
--------- Hashtable ---------
 size     put     get iterate
   10     264     113     113
  100     181     105      76
 1000     260     201      80
10000    1245     134      77
*///:~

 

Insertions for all the Map implementations except for IdentityHashMap get significantly slower as the size of the Map gets large. In general, however, lookup is much cheaper than insertion, which is good because you’ll typically be looking items up much more often than you insert them.

Hashtable performance is roughly the same as HashMap. Since HashMap is intended to replace Hashtable, and thus uses the same underlying storage and lookup mechanism (which you will learn about later) this is not too surprising.

A TreeMap is generally slower than a HashMap. As with TreeSet, a TreeMap is a way to create an ordered list. The behavior of a tree is such that it’s always in order and doesn’t have to be specially sorted. Once you fill a TreeMap, you can call keySet( ) to get a Set view of the keys, then toArray( ) to produce an array of those keys. You can then use the static method Arrays.binarySearch( ) to rapidly find objects in your sorted array. Of course, this only makes sense if the behavior of a HashMap is unacceptable, since HashMap is designed to rapidly find keys. Also, you can easily create a HashMap from a TreeMap with a single object creation or call to putAll(). In the end, when you’re using a Map, your first choice should be HashMap, and only if you need a constantly sorted Map will you need TreeMap.

LinkedHashMap tends to be slower than HashMap for insertions because it maintains the linked list (to preserve insertion order) in addition to the hashed data structure. Because of this list, iteration is faster.

IdentityHashMap has different performance because it uses == rather than equals( ) for comparisons. WeakHashMap is described later in this chapter.



[1] Krzysztof Sobolewski assisted me in figuring out the generics for this example.


Shannon -jj Behrens

Posts: 12
Nickname: jjinux
Registered: Aug, 2005

Re: The Final Performance Testing Example Posted: Aug 24, 2005 12:23 AM
Reply to this message Reply
Maybe I'm much more ignorant about templates than I think I am, but I'm guessing the following line has one too many "<" characters:

private List<Test<<C>> tests;

Michael Stover

Posts: 28
Nickname: mstover
Registered: Jul, 2005

General question about static vs latent typing Posted: Aug 24, 2005 12:33 AM
Reply to this message Reply
I think about this issue a lot, and I've read all your musings on it. The issue for me is this: I like latent typing - it makes my job of typing code easier and faster and, when I'm typing code, I'm very unlikely to make a typing error that a compiler catches.

But, I work with other people's code 90% of the time. Big huge monster codebases of other peoples horrible, awful code. Were that code in Python, I don't know what I would do. Much of this code is in jsp's, which has most of the same problem - ie my IDE can't effectively help me with it. When I refactor some code, I have to do a manual search for the repercussions. If I'm trying to read a jsp, I have to laboriously find the various statically imported JSP's, read their code to understand the file I'm working on.

And now Guido talks about adding optional static typing to Python - presumably for the sake of folks like me. But I was thinking the opposite tack would be much preferable - add optional latent typing to Java.

Generics is not optional latent typing, of course - it's actually a way to strengthen static typing.

I'm thinking of a type something like Object<duck> that the compiler gives a pass to, and furthermore the compiler will convert all calls to such an object to a dynamic method call. Ie, like how Groovy implements its latent typing abilities.

But, by doing this, I have all the niceties of static typing for my run-of-the-mill code - ie my libraries and refactored extracted methods and such, but I can have a few references that are latently typed that the compiler ignores. These are most likely variables of limited scope - ie unlikely to infect the whole codebase with IDE-killing uncertainty.

Imagine if Map objects returned Object<duck> from the get() method. Or maybe it'd be a special implementation of Map - no need to give up the strongly typed Maps, they are useful now and then, after all.

It just seems to me that the ratio of staticly typed code to latently typed code is closer to 80% static/20% latent than the reverse. And we'd be better off adding option latent typing to Java than optional static typing to python.

Someone tell me why I'm all wet (or maybe I'm only catching up to where everyone else is) :-)

Shannon -jj Behrens

Posts: 12
Nickname: jjinux
Registered: Aug, 2005

Re: The Final Performance Testing Example Posted: Aug 24, 2005 12:36 AM
Reply to this message Reply
By the way, thank you very much for the summaries at the end of this post. Picking the right container has always been a put off for me, since I don't code Java on a daily basis. Coming from the Python world, where I need only think of "[]" and rarely bother to pick a different implementation of list, it's nice to know what my default tool of choice should be when coding Java.

Bruce Eckel

Posts: 875
Nickname: beckel
Registered: Jun, 2003

Re: The Final Performance Testing Example Posted: Aug 24, 2005 2:14 AM
Reply to this message Reply
> Maybe I'm much more ignorant about templates than I think
> I am, but I'm guessing the following line has one too many
> "<" characters:
>
> private List<Test<<C>> tests;

Yes. The original code is OK, but a wedgie got lost in translation to HTML.

Bruce Eckel

Posts: 875
Nickname: beckel
Registered: Jun, 2003

Re: The Final Performance Testing Example Posted: Aug 24, 2005 2:18 AM
Reply to this message Reply
> By the way, thank you very much for the summaries at the
> end of this post. Picking the right container has always
> been a put off for me, since I don't code Java on a daily
> basis. Coming from the Python world, where I need only
> think of "[]" and rarely bother to pick a different
> implementation of list, it's nice to know what my default
> tool of choice should be when coding Java.


What you're seeing here is the section that goes into the book, so the summaries (and the examples) were created to do just that.

I do find it quite fascinating to understand the motivation behind these things. It's also interesting to see the variety of approaches. The Python approach is basically "here's a list, don't worry about it," so it's more of a separation of interface and implementation. So it would be possible, for example, for the Python system to decide under
the covers that it should swap out list implementations to make things more efficient. Java is a bit more low level, and the C++ STL is even moreso. It really is a language philosophy issue: what's the most important factor to the language or library designer.

Bruce Eckel

Posts: 875
Nickname: beckel
Registered: Jun, 2003

Re: General question about static vs latent typing Posted: Aug 24, 2005 2:40 AM
Reply to this message Reply
> Someone tell me why I'm all wet (or maybe I'm only
> catching up to where everyone else is) :-)

If C# adds optional latent typing and Microsoft makes a big deal about it, then we might see it in Java :-)

I think the answer will be "that's not Java." Java's verbosity is definitely one of its pain points, but this would be such a fundamental change that I couldn't ever see it happening. My suspicion (partly based on the fact that several top language developers left Sun immediately after J2SE5 shipped) is that J2SE5 will comprise the last big language change to Java. For one thing, annotations provide some pretty good hooks for extending the language without changing the core. But if you look at how difficult it was to make such a fundamental change as J2SE5, and the compromises involved, I think that the language is just too brittle too support any kind of fundumental change hereafter.

You probably want some other language. You might look at Nice.

Michel Parisien

Posts: 25
Nickname: kriggs
Registered: Jul, 2005

Re: The Final Performance Testing Example Posted: Aug 24, 2005 8:01 AM
Reply to this message Reply
I believe that it shouldn't be cdiggins' job to narrow his focus, particularly this early in the game. The idea of an "all purpose" language is ok, as long as it doesn't bloat. If however Heron was inspired by a single application, it would become very good for that application and less good for all other applications.

Michel Parisien

Posts: 25
Nickname: kriggs
Registered: Jul, 2005

Re: The Final Performance Testing Example Posted: Aug 24, 2005 8:03 AM
Reply to this message Reply
I swear I posted that last comment for another thread. Could it be that artima glitched on me?

Bruce Eckel

Posts: 875
Nickname: beckel
Registered: Jun, 2003

Re: The Final Performance Testing Example Posted: Aug 24, 2005 9:36 AM
Reply to this message Reply
Bill told me there was a disk crash and he was reposting some messages by hand. This might have had to do with that. I've let him know.

Bill Venners

Posts: 2249
Nickname: bv
Registered: Jan, 2002

Re: The Final Performance Testing Example Posted: Aug 24, 2005 11:58 AM
Reply to this message Reply
> I swear I posted that last comment for another thread.
> Could it be that artima glitched on me?


I've never seen such a glitch in the past. And I can't explain it based on the disk crash. All I did to recover from the disk crash was build a database out of a one day old dump file, a few hours before the ISP finally gave me access to the old disk and I got the latest dump file. So I had to put some data back by hand. I added about 24 new users back in, some user modifications, and then today I logged in as the folks who posted messages and put them back. But none of that should have interfered with you, so I've no explanation.

Oh wait, there is one possibility. If you replied by clicking Reply to a page that had been cached during that one day of data I had to restore by hand, then this could happen, maybe. Trouble is it is a different thread, and the thread IDs didn't change, just some of the message IDs. So I'm not certain this could explain it. But basically some of the message IDs of messages that were posted in the day I lost the data were reused the next day for other messages. So replying to a message ID, if you were using a cached page, could end up being a different message.

I was very frustrated that it took 33 hours to convince my ISP to mount the bad disk so I could see what I could get off of it. Finally after 30 hours of down time, I gave up and went with the one day old backup. The 3 hours later they mounted the disk and there was the most recent dump file, made three hours before the crash. Had to spend a lot of time restoring stuff from that missing day, and then this may have been caused by it. Sorry about the mix up, but you can just refresh the browser page and try replying again in the other thread and you should be fine.

Michael Stover

Posts: 28
Nickname: mstover
Registered: Jul, 2005

Re: General question about static vs latent typing Posted: Aug 24, 2005 3:44 PM
Reply to this message Reply
> > Someone tell me why I'm all wet (or maybe I'm only
> > catching up to where everyone else is) :-)
>
> [snip]
>
> I think the answer will be "that's not Java." ... I think that the
> language is just too brittle too support any kind of
> fundumental change hereafter.

I have no doubt you are right about Java. My real interest is in the more general question - which would be preferable, a dynamically typed language with optional static typing, or a statically typed language with optional dynamic typing.

>
> You probably want some other language. You might look at
> Nice.

I am looking for my next language (Mozart-Oz has had my eye for a while now), but I really do have an affinity for working in highly supportive IDE's. Plus I'm having fun with AOP lately, and I'm not sure where else I can get that :-)

Flat View: This topic has 11 replies on 1 page
Topic: Messaging may not be the way to Build a Distrbuted System Previous Topic   Next Topic Topic: Dependency Injection and Jini Configurations


Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us