package com.buschmais.xo.neo4j.test.performance; import static com.buschmais.xo.api.Transaction.TransactionAttribute; import static java.util.Arrays.asList; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.neo4j.graphdb.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.buschmais.xo.api.ConcurrencyMode; import com.buschmais.xo.api.ValidationMode; import com.buschmais.xo.api.XOManager; import com.buschmais.xo.api.bootstrap.XOUnit; import com.buschmais.xo.neo4j.embedded.api.EmbeddedNeo4jDatastoreSession; import com.buschmais.xo.neo4j.test.AbstractNeo4jXOManagerTest; import com.buschmais.xo.neo4j.test.relation.typed.composite.TreeNode; import com.buschmais.xo.neo4j.test.relation.typed.composite.TreeNodeRelation; @RunWith(Parameterized.class) public class XoVsNativePerformanceIT extends AbstractNeo4jXOManagerTest { private static final Logger LOGGER = LoggerFactory.getLogger(XoVsNativePerformanceIT.class); private static final int TREE_DEPTH = 7; private static final int NUMBER_OF_RUNS = 10; public XoVsNativePerformanceIT(XOUnit xoUnit) { super(xoUnit); LOGGER.info("Running using URI " + xoUnit.getUri().toString()); } @Parameterized.Parameters public static Collection<Object[]> getXOUnits() throws URISyntaxException { return xoUnits(asList(TreeNode.class, TreeNodeRelation.class), Collections.emptyList(), ValidationMode.NONE, ConcurrencyMode.SINGLETHREADED, TransactionAttribute.NONE); } private class Measurement { private final long counter; private final long duration; private final double speed; public Measurement(long counter, long start, long stop) { super(); this.counter = counter; this.duration = stop - start; speed = (counter * 1000.0) / duration; } public long getCounter() { return counter; } public long getDuration() { return duration; } public double getSpeed() { return speed; } } @Before public void initialize() { try (XOManager xoManager = getXoManagerFactory().createXOManager()) { EmbeddedNeo4jDatastoreSession datastoreSession = xoManager.getDatastoreSession(EmbeddedNeo4jDatastoreSession.class); GraphDatabaseService graphDatabaseService = datastoreSession.getGraphDatabaseService(); Transaction transaction = graphDatabaseService.beginTx(); Node n1 = graphDatabaseService.createNode(); Node n2 = graphDatabaseService.createNode(); n1.createRelationshipTo(n2, DynamicRelationshipType.withName("BOOTSTRAP")); transaction.success(); transaction.close(); } } private List<Measurement> xoMeasurements = new ArrayList<>(); private List<Measurement> nativeMeasurements = new ArrayList<>(); @Test public void test() { try (XOManager xoManager = getXoManagerFactory().createXOManager()) { nativeMeasurements = runNative(xoManager); xoMeasurements = runXO(xoManager); printResults(); } } private List<Measurement> runXO(final XOManager xoManager) { ApiUnderTest<TreeNode, TreeNodeRelation> xoApi = new ApiUnderTest<TreeNode, TreeNodeRelation>() { @Override public void begin() { xoManager.currentTransaction().begin(); } @Override public void commit() { xoManager.currentTransaction().commit(); } @Override public TreeNode createEntity() { return xoManager.create(TreeNode.class); } @Override public void setName(TreeNode treeNode, String value) { treeNode.setName(value); } @Override public TreeNodeRelation createRelation(TreeNode parent, TreeNode child) { return xoManager.create(parent, TreeNodeRelation.class, child); } @Override public String toString() { return "XO"; } }; return run(xoApi); } private List<Measurement> runNative(final XOManager xoManager) { final GraphDatabaseService graphDatabaseService = xoManager.getDatastoreSession(EmbeddedNeo4jDatastoreSession.class).getGraphDatabaseService(); ApiUnderTest<Node, Relationship> nativeApi = new ApiUnderTest<Node, Relationship>() { private Transaction transaction; @Override public void begin() { transaction = graphDatabaseService.beginTx(); } @Override public void commit() { transaction.success(); transaction.close(); } @Override public Node createEntity() { Node node = graphDatabaseService.createNode(DynamicLabel.label(TreeNode.class.getSimpleName())); return node; } @Override public void setName(Node node, String value) { node.setProperty("name", value); } @Override public Relationship createRelation(Node parent, Node child) { DynamicRelationshipType relationshipType = DynamicRelationshipType.withName(TreeNodeRelation.class.getSimpleName()); if (parent.hasRelationship(relationshipType, Direction.OUTGOING)) { parent.getSingleRelationship(relationshipType, Direction.OUTGOING).delete(); } Relationship relationshipTo = parent.createRelationshipTo(child, relationshipType); return relationshipTo; } @Override public String toString() { return "Native"; } }; return run(nativeApi); } private <Entity, Relation> List<Measurement> run(ApiUnderTest<Entity, Relation> apiUnderTest) { LOGGER.info("Starting run with API '" + apiUnderTest + "'"); List<Measurement> measurements = new ArrayList<>(NUMBER_OF_RUNS); for (int i = 0; i < NUMBER_OF_RUNS; i++) { dropDatabase(); apiUnderTest.begin(); long start = System.currentTimeMillis(); Entity root = apiUnderTest.createEntity(); apiUnderTest.setName(root, "1"); long counter = 1; counter += addChildren(apiUnderTest, root, 2, "1"); apiUnderTest.commit(); long stop = System.currentTimeMillis(); Measurement measurement = new Measurement(counter, start, stop); LOGGER.info( "counter=" + measurement.getCounter() + ", time=" + measurement.getDuration() + "ms" + ", speed=" + measurement.getSpeed() + " vertices/s"); measurements.add(measurement); } LOGGER.info("Finished run with API " + apiUnderTest); return measurements; } private <Entity, Relation> long addChildren(ApiUnderTest<Entity, Relation> apiUnderTest, Entity parent, int i, String namePrefix) { if (i > TREE_DEPTH) { return 0; } long counter = 0; for (int id = 1; id <= i; id++) { String name = namePrefix + id; Entity child = apiUnderTest.createEntity(); apiUnderTest.setName(child, name); apiUnderTest.createRelation(parent, child); counter++; counter += addChildren(apiUnderTest, child, i + 1, name); } return counter; } private void printResults() { System.out.println("==========="); System.out.println("XO Results:"); System.out.println("==========="); print(xoMeasurements); System.out.println(); System.out.println("==============="); System.out.println("Native Results:"); System.out.println("==============="); print(nativeMeasurements); } private void print(List<Measurement> measurements) { System.out.println("Counter\tDuration [ms]\tSpeed [vertices/s]"); System.out.println("--------------------------------------------------"); long durationSum = 0; double speedSum = 0.0; for (Measurement measurement : measurements) { long counter = measurement.getCounter(); long duration = measurement.getDuration(); double speed = measurement.getSpeed(); String format = MessageFormat.format("{0}\t{1}\t{2,number,#.##}", counter, duration, speed); System.out.println(format); speedSum += speed; durationSum += duration; } System.out.println("--------------------------------------------------"); double durationAvg = (double) durationSum / (double) measurements.size(); System.out.println(MessageFormat.format("average duration={0,number,#.##} ms", durationAvg)); double speedAvg = speedSum / measurements.size(); System.out.println(MessageFormat.format("average speed={0,number,#.##} vertices/s", speedAvg)); } }