package org.flexo.test; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.List; import java.util.Vector; import org.openflexo.model.ModelContext; import org.openflexo.model.annotations.Adder; import org.openflexo.model.annotations.Getter; import org.openflexo.model.annotations.Getter.Cardinality; import org.openflexo.model.annotations.ModelEntity; import org.openflexo.model.annotations.Remover; import org.openflexo.model.annotations.Setter; import org.openflexo.model.exceptions.ModelDefinitionException; import org.openflexo.model.factory.ModelFactory; /** * Class of tests to measure the performance of pamela vs regular written classes. * * Tests are made of: * <ol> * <li>A model interface and a default implementation</li> * <li>Test runnables: one kind of operation performed once with a pamela implementation and once with regular classes</li> * <li>Tests: which simply executes the different runnables in a given order</li> * </ol> * The execution time and the used memory are automatically computed and printed to the error console. Theses measures are to be taken with * great care since execution time can always be influenced by the workload of the computer on which it is executed, while the memory * measure can always be influenced by the GC running in a separate thread. * * @author Guillaume * */ public class PerformanceTest { /** * The result of a TestRunnable. * * @author Guillaume * */ public static class TestRunnableResult { public Object returned; public long usedMemory; public long usedTime; } /** * An operation that needs to be run with pamela and regular classes. Different operations can provide performance measure to compare * pamela agains traditional classes. * * @author Guillaume * */ public static interface TestRunnable { /** * The operation that will be executed. * * @param factory * if the factory is not null, then it should be used to build pamela objects, else the default implementation should * rather be used. * @return the root object of the model (in order to keep a reference to the model and avoid the GC to garbage collect the memory * before the memory footprint has been computed). */ public Object run(ModelFactory factory); } /** * A simple model object which defines a hierarchy. * * @author Guillaume * */ @ModelEntity public static interface ModelObject { public static final String CHILDREN = "children"; public static final String PARENT = "parent"; @Getter(value = PARENT, inverse = CHILDREN) public ModelObject getParent(); @Setter(PARENT) public void setParent(ModelObject parent); @Getter(value = CHILDREN, cardinality = Cardinality.LIST, inverse = PARENT) public List<ModelObject> getChildren(); @Setter(CHILDREN) public void setChildren(List<ModelObject> children); @Adder(CHILDREN) public void addToChildren(ModelObject child); @Remover(CHILDREN) public void removeFromChildren(ModelObject child); } /** * A default implementation. * * @author Guillaume * */ public static class ModelObjectImpl implements ModelObject { private List<ModelObject> children; private ModelObject parent; @Override public ModelObject getParent() { return parent; } @Override public void setParent(ModelObject parent) { if (this.parent != parent) { if (this.parent != null) { this.parent.removeFromChildren(this); } this.parent = parent; if (this.parent != null) { this.parent.addToChildren(this); } } } @Override public List<ModelObject> getChildren() { return children; } @Override public void setChildren(List<ModelObject> children) { this.children = children; } @Override public void addToChildren(ModelObject child) { if (children == null) { children = new ArrayList<PerformanceTest.ModelObject>(); } if (!children.contains(child)) { children.add(child); child.setParent(this); } } @Override public void removeFromChildren(ModelObject child) { if (children == null) { return; } if (children.contains(child)) { children.remove(child); child.setParent(null); } } } /** * Simply instantiate a rather big model (about 100 000 objects with a tree depth of 7). * * @author Guillaume * */ public static class BuildBasicModelRunnable implements TestRunnable { @Override public Object run(ModelFactory factory) { return buildModel(5, 7, factory); // 97656 objects } } /** * This test runnable removes and re-adds recursively all children of the model. * * @author Guillaume * */ public static class ManipulateBasicModelRunnable implements TestRunnable { private void removeAndReaddChildren(ModelObject object) { if (object.getChildren() != null && object.getChildren().size() > 0) { List<ModelObject> children = new ArrayList<PerformanceTest.ModelObject>(object.getChildren()); for (ModelObject child : children) { object.removeFromChildren(child); removeAndReaddChildren(child); object.addToChildren(child); } } } @Override public Object run(ModelFactory factory) { ModelObject object = buildModel(5, 4, factory); removeAndReaddChildren(object); return object; } } /** * A really simple model holding 1000 objects with a single level of hierarchy (1 root object and 999 children) * * @author Guillaume * */ public static class DumbModelRunnable implements TestRunnable { @Override public Object run(ModelFactory factory) { ModelObject // object = buildModel(99999, 1, factory); object = buildModel(999, 1, factory);// 1000 objects return object; } } /** * A method to build a hierarchical model where all objects have <code>numbeOfChildren</code> children (except the leaves of the tree) * and the depth of the tree is defined by <code>depth</code> * * @param numberOfChildren * the number of children each objects of the hierarchy should have * @param depth * the depth of the hierarchy * @param factory * the factory to use to build the model with pamela, else the factory is null * @return the root object of the model. */ public static ModelObject buildModel(int numberOfChildren, int depth, ModelFactory factory) { ModelObject object; if (factory != null) { object = factory.newInstance(ModelObject.class, null); } else { object = new ModelObjectImpl(); } if (depth > 0) { for (int i = 0; i < numberOfChildren; i++) { ModelObject child = buildModel(numberOfChildren, depth - 1, factory); object.addToChildren(child); } } return object; } /** * Runs a TestRunnable <code>runnable</code> with the given <code>factory</code>. The factory may be null. * * @param factory * the factory to pass to the TestRunnable; may be null. * @param runnable * the TestRunnable to run * @return the TestResult, ie, time execution, memory footprint and the root object of the model */ private TestRunnableResult runRunnable(ModelFactory factory, TestRunnable runnable) { TestRunnableResult result = new TestRunnableResult(); long startMem, endMem, start, end; startMem = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); start = System.currentTimeMillis(); result.returned = runnable.run(factory); end = System.currentTimeMillis(); result.usedTime = end - start; endMem = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); result.usedMemory = endMem - startMem; return result; } /** * Method to invoke to run a test runnable 20 times in a row, 10 times using pamela objects and 10 times using regular objects. * Eventually, the method prints out the result. * * @param runnable * the test runnable to execute. * @param factory * the factory to pass to the TestRunnable when using pamela objects. Cannot be null */ private void testModel(TestRunnable runnable, ModelFactory factory, ModelFactory factory2) { long proxyTime = 0, proxyMem = 0, regularTime = 0, regularMem = 0; for (int i = 0; i < 10; i++) { TestRunnableResult result = runRunnable(factory2, runnable); if (i > 0) { regularTime += result.usedTime; regularMem += result.usedMemory; } System.gc(); try { Thread.sleep(200); } catch (InterruptedException e) { } result = runRunnable(factory, runnable); if (i > 0) { proxyTime += result.usedTime; proxyMem += result.usedMemory; } result = null; System.gc(); try { Thread.sleep(200); } catch (InterruptedException e) { } } System.err.println("Test " + runnable.getClass().getSimpleName()); System.err.println("\tUsing PAMELA " + " took: " + proxyTime + "ms"); System.err.println("\tUsing regular classes took: " + regularTime + "ms"); System.err.println("\tPAMELA is " + (double) proxyTime / regularTime + " slower than regular classes"); System.err.println("\tUsing PAMELA " + " took: " + proxyMem + " bytes"); System.err.println("\tUsing regular classes took: " + regularMem + " bytes"); System.err.println("\tPAMELA eats " + (double) proxyMem / regularMem + " more memory than regular classes"); } public static void main(String[] args) throws ModelDefinitionException { PerformanceTest test = new PerformanceTest(); ModelContext mapping = new ModelContext(ModelObject.class); ModelFactory factory = new ModelFactory(mapping); factory.setListImplementationClass(ArrayList.class); ModelFactory factory2 = new ModelFactory(mapping); factory.setListImplementationClass(Vector.class); test.testModel(new DumbModelRunnable(), factory, factory2); test.testModel(new BuildBasicModelRunnable(), factory, factory2); test.testModel(new ManipulateBasicModelRunnable(), factory, factory2); } }