Java tutorial
/** * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.leo.benchmark; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.lang.management.ManagementFactory; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.Timer; import org.apache.commons.collections4.list.TreeList; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.labels.AbstractCategoryItemLabelGenerator; import org.jfree.chart.labels.ItemLabelAnchor; import org.jfree.chart.labels.ItemLabelPosition; import org.jfree.chart.labels.StandardCategoryItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.renderer.category.BarRenderer; import org.jfree.chart.title.TextTitle; import org.jfree.data.category.CategoryDataset; import org.jfree.data.category.DefaultCategoryDataset; import org.jfree.ui.TextAnchor; import org.leo.list.CombinedList; /** * Collection Benchmark * * @author Leo Lewis */ public class Benchmark { /** * Time in ms after which the benchmark task is considered timeout and is * stopped */ private long timeout; /** Is the given benchmark task timeout */ private volatile boolean isTimeout; /** * Number of elements to populate the collection on which the benchmark will * be launched */ private int populateSize; /** Collection implementation to be tested */ private Collection<String> collection; /** List implementation to be tested */ private List<String> list; /** * Default context used for each benchmark test (will populate the tested * collection before launching the bench) */ private List<String> defaultCtx; /** Benchmark results */ private Map<String, Map<Class<? extends Collection<?>>, Long>> benchResults; /** Memory results */ private Map<Class<? extends Collection<?>>, Long> memoryResults; /** * Constructor * * @param timeout * @param populateSize */ public Benchmark(long timeout, int populateSize) { this.timeout = timeout; this.populateSize = populateSize; defaultCtx = new ArrayList<String>(); for (int i = 0; i < populateSize; i++) { defaultCtx.add(Integer.toString(i % 100)); } benchResults = new HashMap<String, Map<Class<? extends Collection<?>>, Long>>(); memoryResults = new HashMap<Class<? extends Collection<?>>, Long>(); } /** * Run the benchmark on the given collection * * @param collectionTested the collection (if it's a List, some additional * bench will be done) * @throws IllegalAccessException * @throws InstantiationException */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void run(Class<? extends Collection> collectionClass) { try { long startTime = System.currentTimeMillis(); Constructor<? extends Collection> constructor = collectionClass .getDeclaredConstructor((Class<?>[]) null); constructor.setAccessible(true); collection = (Collection<String>) constructor.newInstance(); System.out.println("Performances of " + collection.getClass().getCanonicalName() + " populated with " + populateSize + " elt(s)"); System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); // some collection used in some benchmark cases final Collection<String> col = new ArrayList<String>(); for (int i = 0; i < 1000; i++) { col.add(Integer.toString(i % 30)); } // Collection benchmark execute(new BenchRunnable() { @Override public void run(int i) { collection.add(Integer.toString(i % 29)); } }, populateSize, "add " + populateSize + " elements"); execute(new BenchRunnable() { @Override public void run(int i) { collection.remove(Integer.toString(i)); } }, Math.max(1, populateSize / 10), "remove " + Math.max(1, populateSize / 10) + " elements given Object"); execute(new BenchRunnable() { @Override public void run(int i) { collection.addAll(col); } }, Math.min(populateSize, 1000), "addAll " + Math.min(populateSize, 1000) + " times " + col.size() + " elements"); execute(new BenchRunnable() { @Override public void run(int i) { collection.contains(collection.size() - i - 1); } }, Math.min(populateSize, 1000), "contains " + Math.min(populateSize, 1000) + " times"); execute(new BenchRunnable() { @Override public void run(int i) { collection.removeAll(col); } }, Math.min(populateSize, 10), "removeAll " + Math.min(populateSize, 10) + " times " + col.size() + " elements"); execute(new BenchRunnable() { @Override public void run(int i) { collection.iterator(); } }, populateSize, "iterator " + populateSize + " times"); execute(new BenchRunnable() { @Override public void run(int i) { collection.containsAll(col); } }, Math.min(populateSize, 5000), "containsAll " + Math.min(populateSize, 5000) + " times"); execute(new BenchRunnable() { @Override public void run(int i) { collection.toArray(); } }, Math.min(populateSize, 5000), "toArray " + Math.min(populateSize, 5000) + " times"); execute(new BenchRunnable() { @Override public void run(int i) { collection.clear(); } }, 1, "clear"); execute(new BenchRunnable() { @Override public void run(int i) { collection.retainAll(col); } }, Math.min(populateSize, 10), "retainAll " + Math.min(populateSize, 10) + " times"); // List benchmark if (collection instanceof List) { list = (List<String>) collection; execute(new BenchRunnable() { @Override public void run(int i) { list.add((i), Integer.toString(i)); } }, populateSize, "add at a given index " + populateSize + " elements"); execute(new BenchRunnable() { @Override public void run(int i) { list.addAll((i), col); } }, Math.min(populateSize, 1000), "addAll " + Math.min(populateSize, 1000) + " times " + col.size() + " elements at a given index"); execute(new BenchRunnable() { @Override public void run(int i) { list.get(i); } }, Math.min(populateSize, 50000), "get " + Math.min(populateSize, 50000) + " times"); execute(new BenchRunnable() { @Override public void run(int i) { list.indexOf(i); } }, Math.min(populateSize, 5000), "indexOf " + Math.min(populateSize, 5000) + " times"); execute(new BenchRunnable() { @Override public void run(int i) { list.lastIndexOf(i); } }, Math.min(populateSize, 5000), "lastIndexOf " + Math.min(populateSize, 5000) + " times"); execute(new BenchRunnable() { @Override public void run(int i) { list.set(i, Integer.toString(i % 29)); } }, Math.max(1, populateSize), "set " + Math.max(1, populateSize) + " times"); execute(new BenchRunnable() { @Override public void run(int i) { list.subList(collection.size() / 4, collection.size() / 2); } }, populateSize, "subList on a " + (populateSize / 2 - populateSize / 4) + " elts sublist " + populateSize + " times"); execute(new BenchRunnable() { @Override public void run(int i) { list.listIterator(); } }, populateSize, "listIterator " + populateSize + " times"); execute(new BenchRunnable() { @Override public void run(int i) { list.listIterator(i); } }, populateSize, "listIterator at a given index " + populateSize + " times"); execute(new BenchRunnable() { @Override public void run(int i) { list.remove(list.size() / 2); } }, 10000, "remove " + 10000 + " elements given index (index=list.size()/2)"); } System.out.println( "Benchmark done in " + ((double) (System.currentTimeMillis() - startTime)) / 1000 + "s"); System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); // free memory collection.clear(); } catch (Exception e) { System.err.println("Failed running benchmark on class " + collectionClass.getCanonicalName()); e.printStackTrace(); } collection = null; list = null; heavyGc(); } /** * Execute the current run code loop times. * * @param run code to run * @param loop number of time to run the code * @param taskName name displayed at the end of the task */ @SuppressWarnings({ "unchecked", "rawtypes" }) private void execute(BenchRunnable run, int loop, String taskName) { System.out.print(taskName + " ... "); // set default context collection.clear(); collection.addAll(defaultCtx); // warmup warmUp(); isTimeout = false; // timeout timer Timer timer = new Timer((int) timeout, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { isTimeout = true; // to raise a ConcurrentModificationException or a // NoSuchElementException to interrupt internal work in the List collection.clear(); } }); timer.setRepeats(false); timer.start(); long startTime = System.nanoTime(); int i; for (i = 0; i < loop && !isTimeout; i++) { try { run.run(i); } catch (Exception e) { // on purpose so ignore it } } timer.stop(); long time = isTimeout ? timeout * 1000000 : System.nanoTime() - startTime; System.out.println((isTimeout ? "Timeout (>" + time + "ns) after " + i + " loop(s)" : time + "ns")); // restore default context, // the collection instance might have been // corrupted by the timeout so create a new instance try { Constructor<? extends Collection> constructor = collection.getClass() .getDeclaredConstructor((Class<?>[]) null); constructor.setAccessible(true); collection = constructor.newInstance(); // update the reference if (collection instanceof List) { list = (List<String>) collection; } } catch (Exception e1) { e1.printStackTrace(); } // store the results for display Map<Class<? extends Collection<?>>, Long> currentBench = benchResults.get(taskName); if (currentBench == null) { currentBench = new HashMap<Class<? extends Collection<?>>, Long>(); benchResults.put(taskName, currentBench); } currentBench.put((Class<? extends Collection<String>>) collection.getClass(), time); // little gc to clean up all the stuff System.gc(); } /** * Display benchmark results */ @SuppressWarnings("serial") public void displayBenchmarkResults() { List<ChartPanel> chartPanels = new ArrayList<ChartPanel>(); // sort task by names List<String> taskNames = new ArrayList<String>(benchResults.keySet()); Collections.sort(taskNames); // browse task name, 1 chart per task for (String taskName : taskNames) { // time by class Map<Class<? extends Collection<?>>, Long> clazzResult = benchResults.get(taskName); ChartPanel chartPanel = createChart(taskName, "Time (ns)", clazzResult, new StandardCategoryItemLabelGenerator() { @Override public String generateLabel(CategoryDataset dataset, int row, int column) { String label = " " + dataset.getRowKey(row).toString(); if (dataset.getValue(row, column).equals(timeout * 1000000)) { label += " (Timeout)"; } return label; } }); chartPanels.add(chartPanel); } // display in a JFrame JPanel mainPanel = new JPanel(new GridLayout(chartPanels.size() / 5, 5, 5, 5)); for (ChartPanel chart : chartPanels) { mainPanel.add(chart); } JFrame frame = new JFrame("Collection Implementations Benchmark"); frame.getContentPane().add(new JLabel("Collection Implementations Benchmark. Populate size : " + populateSize + ", timeout : " + timeout + "ms"), BorderLayout.NORTH); frame.getContentPane().add(mainPanel, BorderLayout.CENTER); frame.setSize(900, 500); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } /** * Do some operation to be sure that the internal structure is allocated */ @SuppressWarnings("rawtypes") private void warmUp() { collection.remove(collection.iterator().next()); if (collection instanceof List) { collection.remove(0); ((List) collection).indexOf(((List) collection).get(0)); } collection.iterator(); collection.toArray(); } /** * Create a chartpanel * * @param title title * @param dataName name of the data * @param clazzResult data mapped by classes * @param catItemLabelGenerator label generator * @return the chartPanel */ @SuppressWarnings("serial") private ChartPanel createChart(String title, String dataName, Map<Class<? extends Collection<?>>, Long> clazzResult, AbstractCategoryItemLabelGenerator catItemLabelGenerator) { // sort data by class name List<Class<? extends Collection<?>>> clazzes = new ArrayList<Class<? extends Collection<?>>>( clazzResult.keySet()); Collections.sort(clazzes, new Comparator<Class<? extends Collection<?>>>() { @Override public int compare(Class<? extends Collection<?>> o1, Class<? extends Collection<?>> o2) { return o1.getCanonicalName().compareTo(o2.getCanonicalName()); } }); DefaultCategoryDataset dataSet = new DefaultCategoryDataset(); // add the data to the dataset for (Class<? extends Collection<?>> clazz : clazzes) { dataSet.addValue(clazzResult.get(clazz), clazz.getName(), title.split(" ")[0]); } // create the chart JFreeChart chart = ChartFactory.createBarChart3D(null, null, dataName, dataSet, PlotOrientation.HORIZONTAL, false, true, false); chart.addSubtitle(new TextTitle(title)); // some customization in the style CategoryPlot plot = chart.getCategoryPlot(); plot.setBackgroundPaint(new Color(250, 250, 250)); plot.setDomainGridlinePaint(new Color(255, 200, 200)); plot.setRangeGridlinePaint(Color.BLUE); plot.getDomainAxis().setVisible(false); plot.getRangeAxis().setLabelFont(new Font("arial", Font.PLAIN, 10)); BarRenderer renderer = (BarRenderer) chart.getCategoryPlot().getRenderer(); // display the class name in the bar chart for (int i = 0; i < clazzResult.size(); i++) { renderer.setSeriesItemLabelGenerator(i, new StandardCategoryItemLabelGenerator() { @Override public String generateLabel(CategoryDataset dataset, int row, int column) { String label = " " + dataset.getRowKey(row).toString(); if (dataset.getValue(row, column).equals(timeout * 1000000)) { label += " (Timeout)"; } return label; } }); renderer.setSeriesItemLabelsVisible(i, true); ItemLabelPosition itemPosition = new ItemLabelPosition(ItemLabelAnchor.CENTER, TextAnchor.CENTER_LEFT, TextAnchor.CENTER_LEFT, 0.0); renderer.setSeriesPositiveItemLabelPosition(i, itemPosition); renderer.setSeriesNegativeItemLabelPosition(i, itemPosition); } ItemLabelPosition itemPosition = new ItemLabelPosition(ItemLabelAnchor.OUTSIDE9, TextAnchor.CENTER_LEFT, TextAnchor.CENTER_LEFT, 0.0); renderer.setPositiveItemLabelPositionFallback(itemPosition); renderer.setNegativeItemLabelPositionFallback(itemPosition); renderer.setShadowVisible(false); // create the chartpanel ChartPanel chartPanel = new ChartPanel(chart); chart.setBorderVisible(true); return chartPanel; } /** * @param collectionClass * @throws NoSuchMethodException * @throws SecurityException */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void runMemoryBench(List<Class<? extends Collection>> collectionClasses) { for (Class<? extends Collection> clazz : collectionClasses) { try { // run some gc heavyGc(); long usedMemory = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); Constructor<? extends Collection> constructor = clazz.getDeclaredConstructor((Class<?>[]) null); constructor.setAccessible(true); // do the test on 100 objects, to be more accurate for (int i = 0; i < 100; i++) { this.collection = (Collection<String>) constructor.newInstance(); // polulate collection.addAll(defaultCtx); warmUp(); } // measure size long objectSize = (long) ((ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed() - usedMemory) / 100f); System.out.println(clazz.getCanonicalName() + " Object size : " + objectSize + " bytes"); memoryResults.put((Class<? extends Collection<?>>) clazz, objectSize); collection.clear(); collection = null; } catch (Exception e) { System.err.println("Failed running benchmark on class " + clazz.getCanonicalName()); e.printStackTrace(); } } } /** * Force (very) heavy GC */ private void heavyGc() { try { System.gc(); Thread.sleep(200); System.runFinalization(); Thread.sleep(200); System.gc(); Thread.sleep(200); System.runFinalization(); Thread.sleep(1000); System.gc(); Thread.sleep(200); System.runFinalization(); Thread.sleep(200); System.gc(); } catch (InterruptedException ex) { ex.printStackTrace(); } } /** * Display Memory results */ public void displayMemoryResults() { ChartPanel chart = createChart("Memory usage of collections", "Memory usage (bytes) of collections populated by " + populateSize + " element(s)", memoryResults, new StandardCategoryItemLabelGenerator()); JFrame frame = new JFrame("Collection Implementations Benchmark"); frame.getContentPane().add(chart, BorderLayout.CENTER); frame.setSize(900, 500); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } /** * BenchRunnable * * @author Leo Lewis */ private interface BenchRunnable { /** * Runnable that can exploit the current loop index * * @param loopIndex loop index */ public void run(int loopIndex); } /** * Main * * @param args */ public static void main(String[] args) { try { Benchmark benchmark = new Benchmark(15000, 100000); // standard benchmark // benchmark.run(Vector.class); // benchmark.run(TreeList.class); // benchmark.run(ArrayList.class); // benchmark.run(LinkedList.class); // benchmark.run(NodeCachingLinkedList.class); // benchmark.run(CopyOnWriteArrayList.class); // benchmark.run(FastList.class); // benchmark.run(HashSet.class); // benchmark.displayBenchmarkResults(); // set benchmark // benchmark.run(HashSet.class); // benchmark.run(FastSet.class); // benchmark.run(CopyOnWriteArraySet.class); // benchmark.run(TreeSet.class); // benchmark.run(LinkedHashSet.class); // // optional // // benchmark.run(TreeMultiset.class); // // benchmark.run(PriorityQueue.class); // benchmark.displayBenchmarkResults(); // my benchmark benchmark.run(CombinedList.class); benchmark.run(ArrayList.class); benchmark.run(LinkedList.class); benchmark.run(TreeList.class); benchmark.run(HashSet.class); benchmark.displayBenchmarkResults(); // memory benchmark // List<Class<? extends Collection>> classes = new ArrayList<Class<? // extends Collection>>(); // classes.add(Vector.class); // classes.add(TreeList.class); // classes.add(ArrayList.class); // classes.add(LinkedList.class); // classes.add(NodeCachingLinkedList.class); // classes.add(CopyOnWriteArrayList.class); // classes.add(FastList.class); // classes.add(HashSet.class); // classes.add(FastSet.class); // classes.add(CopyOnWriteArraySet.class); // classes.add(TreeSet.class); // classes.add(LinkedHashSet.class); // classes.add(TreeMultiset.class); // classes.add(PriorityQueue.class); // classes.add(CombinedList.class); // benchmark.runMemoryBench(classes); // benchmark.displayMemoryResults(); } catch (Exception e) { e.printStackTrace(); } } }