Caching Example I

When accessing elements from sets of data, it is often the case that some elements are accessed much more frequently than others. In these cases, it is possible to apply caching techniques to speed up access to these frequently accessed elements. This is best demonstrated with the following example.

Consider a CacheTest class that consists mainly of a Map populated with Integer objects. I use Integer objects for convenience to populate the Map with many elements, but the actual object type is of no significance since you use only the hashCode( ) and equals( ) methods, just as the Map does.

Basically, you provide two ways to access the elements of the Map. The first, plain_access(), just calls the Map.get( ) method as normal. The second method, cached_access( ), uses the lower bits of the hash code of the object to obtain an index value into an array. This index is then checked to see whether the object is there. If it is, the corresponding value in a parallel value array is returned. If it’s not, the object is placed there with the value in the corresponding value array.

This is about the simplest example of general cached access. It demonstrates the advantages and pitfalls of cached access. I have selected 10 integers that do not map to the same indexes for the example. Running the class gives a straightforward comparison between the two access methods, and I get the result that the cached access varies significantly depending on the VM used. The access speedups are illustrated in the following table of measurements. Times have been normalized to the JDK 1.2 case for using a HashMap. The first time of each entry is the measurement using a HashMap (not available in JDK 1.1.6), and the second is the measurement using a Hashtable. For any one VM, the cached access is significantly faster.

 

JDK 1.2

JDK 1.3

JDK 1.1.6

JDK 1.2 no JIT

HotSpot 1.0

Plain access(HashMap/Hashtable)

100%/317%

198%/203%

-/444%

1646%/2730%

329%/238%

Cached access(HashMap/Hashtable)

35%/32%

73%/73%

-/32%

1188%/1120%

120%/101%

This test is artificial in that I chose integers where no two map to the same index. If there is more than one integer that maps to the same cache array index, this is called a collision. Clearly, with collisions, performance is not as good because you are constantly entering the code that puts the objects into the cache. Collisions are a general problem with cached data, and you need to minimize them for optimal performance. This can be done by choosing an appropriate mapping function to generate indexes that minimize your collisions:

package tuning.cache;

import java.util.HashMap;
import java.util.Hashtable;
import java.lang.Math;

public class CacheTest
{
  //The cache array for the keys
  static Object[] cache_keys = new Object[128];
  //The array for the values corresponding to cached keys
  static Object[] cache_values = new Object[128];
  //static Hashtable hash = new Hashtable( );
  static HashMap hash = new HashMap( );

  public static void main(String[] args)
  {
    try
    {
      System.out.println("started populating");
      populate( );
      System.out.println("started accessing");
      access_test( );
    }
    catch(Exception e){e.printStackTrace( );}
  }

  public static void populate( )
  {
    for (int i = 0; i < 100000; i++)
      hash.put(new Integer(i), new Integer(i+5));
  }

  public static Object plain_access(Integer i)
  {
    //simple get( ) call to the hash table
    return hash.get(i);
  }

  public static Object cached_access(Integer i)
  {
    //First get access index
    int access = Math.abs(i.hashCode( )) & 127;
    Object o;
    //if the access index has an object, and that object is equal to key
    //then return the corresponding value in the parallel values array.
    if ( (o = cache_keys[access]) == null || !o.equals(i))
    {
      //otherwise, we got a collision. We need to replace the
      //object at that access index with the new one that we
      //get from the hashtable using normal Hashtable.get( ),
      //and then return the value retrieved this way
      if (o != null)
        System.out.println("Collsion between " + o + " and " + i);
      o = hash.get(i);
      cache_keys[access] = i;
      cache_values[access] = o;
      return o;
    }
    else
    {
      return cache_values[access];
    }
  }

  public static void access_test( )
  {
    //Ten integers that do not collide under the mapping scheme
    //This gives best performance behavior for illustration purposes
    Integer a0 = new Integer(6767676);
    Integer a1 = new Integer(33);
    Integer a2 = new Integer(998);
    Integer a3 = new Integer(3333);
    Integer a4 = new Integer(12348765);
    Integer a5 = new Integer(9999);
    Integer a6 = new Integer(66665);
    Integer a7 = new Integer(1234);
    Integer a8 = new Integer(987654);
    Integer a9 = new Integer(3121219);
    Object o1,o2,o3,o4,o5,o6,o7,o8,o9,o0;
    long time = System.currentTimeMillis( );
    for (int i = 0; i < 1000000; i++)
    {
       o1 = plain_access(a0);
       o2 = plain_access(a1);
       o3 = plain_access(a2);
       o4 = plain_access(a3);
       o5 = plain_access(a4);
       o6 = plain_access(a5);
       o7 = plain_access(a6);
       o8 = plain_access(a7);
       o9 = plain_access(a8);
       o0 = plain_access(a9);
    }
    System.out.println("plain access took " +
        (System.currentTimeMillis( )-time));

    time = System.currentTimeMillis( );
    for (int i = 0; i < 1000000; i++)
    {
       o1 = cached_access(a0);
       o2 = cached_access(a1);
       o3 = cached_access(a2);
       o4 = cached_access(a3);
       o5 = cached_access(a4);
       o6 = cached_access(a5);
       o7 = cached_access(a6);
       o8 = cached_access(a7);
       o9 = cached_access(a8);
       o0 = cached_access(a9);
    }
    System.out.println("cached access took " +
        (System.currentTimeMillis( )-time));

  }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.64.128