package test;

import java.io.*;

import javax.xml.stream.*;

import org.codehaus.stax2.*;

Simple typed information access stress test, useful for profiling, as well as for quickly checking high-level performance effects of changes (albeit not very accurately, obviously -- need longer running composite tests for such verifications).

Type of data is auto-detected, and is assumed to be homogenous. Basically, data is either within attributes, or as element content, but not both. In either case structure should be shallow, with the root and only immediate leaf-level elements containing attribute or element data. Type of this data is auto-detected from the first instance; data must be in canonical format to be properly recognized (non-first values can be non-canonical).

/** * Simple typed information access stress test, useful for profiling, as well * as for * quickly checking high-level performance effects of changes (albeit * not very accurately, obviously -- need longer running composite * tests for such verifications). *<p> * Type of data is auto-detected, and is assumed to be homogenous. Basically, * data is either within attributes, or as element content, but not both. * In either case structure should be shallow, with the root and only * immediate leaf-level elements containing attribute or element data. * Type of this data is auto-detected from the first instance; data must * be in canonical format to be properly recognized (non-first values * can be non-canonical). */
public class TestTypedSpeed implements XMLStreamConstants {
Number of repetitions to run per test. Dynamically variable, based on observed runtime, to try to keep it high enough.
/** * Number of repetitions to run per test. Dynamically variable, * based on observed runtime, to try to keep it high enough. */
private int REPS; private final static int TEST_PER_GC = 7; final static int TYPE_BOOLEAN = 1; final static int TYPE_INT = 1;
Let's keep per-run times above 50 milliseconds
/** * Let's keep per-run times above 50 milliseconds */
//final static int MIN_RUN_TIME = 50; final static int MIN_RUN_TIME = 5;
Let's keep per-run times below 300 milliseconds
/** * Let's keep per-run times below 300 milliseconds */
//final static int MAX_RUN_TIME = 300; final static int MAX_RUN_TIME = 1000; final XMLInputFactory mInputFactory; final ByteArrayInputStream mIn;
Data in attributes? If true, yes; if no, in elements
/** * Data in attributes? If true, yes; if no, in elements */
boolean mUseAttr; int mType; private TestTypedSpeed(byte[] data) { mInputFactory = new com.fasterxml.aalto.stax.InputFactoryImpl(); mInputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.FALSE); mInputFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE); // Just in case: mInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); mIn = new ByteArrayInputStream(data); // Ok how should we guestimate speed... perhaps from data size? REPS = 100 + ((8 * 1000 * 1000) / data.length); System.out.println("Based on size, will use "+REPS+" repetitions"); } protected int test() throws Exception { /* First things first: let's determine how data is stored, * as well as what's the first (canonical) value */ mIn.reset(); String firstValue = findFirstValue(mIn); // also sets/clear 'mUseAttr' flag if (mUseAttr) { REPS += REPS; System.out.println("(data stored as attributes: doubling REPS to "+REPS+")"); } else { System.out.println("(data stored as element content)"); } // But how about type? mType = 0; if ("true".equals(firstValue) || "false".equals(firstValue)) { mType = TYPE_BOOLEAN; System.out.println("Type detected as: BOOLEAN"); } else { try { /*int nr =*/ Integer.parseInt(firstValue); // Ok, was a valid int mType = TYPE_BOOLEAN; System.out.println("Type detected as: INT"); } catch (NumberFormatException nex) { } } if (mType == 0) { throw new IllegalArgumentException("Can not auto-detect type from value '"+firstValue+"'"); } return test2(); } private String findFirstValue(InputStream in) throws XMLStreamException { XMLStreamReader sr = mInputFactory.createXMLStreamReader(in); sr.nextTag(); sr.nextTag(); String value; if (sr.getAttributeCount() > 0) { // attributes mUseAttr = true; value = sr.getAttributeValue(0); } else { mUseAttr = false; value = sr.getElementText(); } sr.close(); return value; } private int test2() throws Exception { int i = 0; int total = 0; final int TEST_CASES = 2; while (true) { try { Thread.sleep(150L); } catch (InterruptedException ie) { } int round = (i++ % TEST_CASES); long now = System.currentTimeMillis(); String msg; int sum = 0; switch (round) { case 0: msg = "Access using Stax 1.0"; switch (mType) { case TYPE_BOOLEAN: sum = mUseAttr ? testUntypedBooleanAttr(REPS) : testUntypedBooleanElem(REPS); break; /* case TYPE_INT: sum = mUseAttr ? testUntypedIntAttr(REPS) : testUntypedIntElem(REPS); break; */ default: throw new Error("Internal error"); } break; case 1: msg = "Access using Stax2 Typed API"; switch (mType) { case TYPE_BOOLEAN: sum = mUseAttr ? testTypedBooleanAttr(REPS) : testTypedBooleanElem(REPS); break; /* case TYPE_INT: sum = mUseAttr ? testTypedIntAttr(REPS) : testTypedIntElem(REPS); break; */ default: throw new Error("Internal error"); } break; default: throw new Error("Internal error"); } now = System.currentTimeMillis() - now; if (round == 0) { System.out.println(); } System.out.println("Test '"+msg+"' -> "+now+" msecs (" +sum+" -> "+(total & 0xFF)+")."); total += sum; if ((i % TEST_PER_GC) == 0) { System.out.println("[GC]"); try { Thread.sleep(100L); } catch (InterruptedException ie) { } System.gc(); try { Thread.sleep(200L); } catch (InterruptedException ie) { } /* One more tweak: let's add load if things start * running too fast or slow, to try to get sweet range * of 50 to 250 millisseconds */ if (now < MIN_RUN_TIME) { REPS += (REPS / 5); // 20% up System.out.println("[NOTE: increasing reps, now: "+REPS+"]"); try { Thread.sleep(200L); } catch (InterruptedException ie) { } } else if (now > MAX_RUN_TIME && i > 20) { /* Let's reduce load slower than increase; also, * due to initial warmup, let's not adjust until * we've gone through a few cycles */ REPS -= (REPS / 10); // 10% down System.out.println("[NOTE: decreasing reps, now: "+REPS+"]"); try { Thread.sleep(200L); } catch (InterruptedException ie) { } } } } } /* ///////////////////////////////////////////////////////// // Actual value type access, ones via Stax 1.0 ///////////////////////////////////////////////////////// */ protected int testUntypedBooleanAttr(int reps) throws Exception { int total = 0; for (int i = 0; i < reps; ++i) { XMLStreamReader2 sr = constructAndFindRoot(); while (sr.nextTag() == START_ELEMENT) { int c = sr.getAttributeCount(); while (--c >= 0) { String str = sr.getAttributeValue(c).trim(); if (str.equals("true")) { ++total; } else if (str.equals("false") || str.equals("0")) { ; } else if (str.equals("1")) { ++total; } else { throw new XMLStreamException("Illegal value '"+str+"', not boolean"); } } } sr.close(); } return total; } protected int testUntypedBooleanElem(int reps) throws Exception { int total = 0; for (int i = 0; i < reps; ++i) { XMLStreamReader2 sr = constructAndFindRoot(); while (sr.nextTag() == START_ELEMENT) { String str = sr.getElementText().trim(); if (str.equals("true")) { ++total; } else if (str.equals("false") || str.equals("0")) { ; } else if (str.equals("1")) { ++total; } else { throw new XMLStreamException("Illegal value '"+str+"', not boolean"); } } sr.close(); } return total; } /* ///////////////////////////////////////////////////////// // Actual value type access, ones via Stax2 Typed API ///////////////////////////////////////////////////////// */ protected int testTypedBooleanAttr(int reps) throws Exception { int total = 0; for (int i = 0; i < reps; ++i) { XMLStreamReader2 sr = constructAndFindRoot(); while (sr.nextTag() == START_ELEMENT) { int c = sr.getAttributeCount(); while (--c >= 0) { if (sr.getAttributeAsBoolean(c)) { ++total; } } } sr.close(); } return total; } protected int testTypedBooleanElem(int reps) throws Exception { int total = 0; for (int i = 0; i < reps; ++i) { XMLStreamReader2 sr = constructAndFindRoot(); while (sr.nextTag() == START_ELEMENT) { if (sr.getElementAsBoolean()) { ++total; } } sr.close(); } return total; } /* ///////////////////////////////////////////////////////// // Helper methods ///////////////////////////////////////////////////////// */ XMLStreamReader2 constructAndFindRoot() throws XMLStreamException { mIn.reset(); XMLStreamReader sr = mInputFactory.createXMLStreamReader(mIn); if (sr.nextTag() != START_ELEMENT) { throw new XMLStreamException("Couldn't locate root"); } ; // points to root now return (XMLStreamReader2) sr; } static byte[] readData(File file) throws IOException { InputStream fin = new FileInputStream(file); byte[] buf = new byte[4000]; ByteArrayOutputStream bos = new ByteArrayOutputStream(4000); int count; while ((count = fin.read(buf)) > 0) { bos.write(buf, 0, count); } fin.close(); return bos.toByteArray(); } public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage: java ... <file>"); System.exit(1); } byte[] data = readData(new File(args[0])); System.out.println(" -> "+data.length+" bytes read."); new TestTypedSpeed(data).test(); } }