Skip to content

Example : Selecting the fastest algorithm result

johnmcclean-aol edited this page Jan 27, 2015 · 6 revisions

If we wanted to test the relative performance of algorithms that solve the same problem, we can setup multiple reactive dataflows, one for each algorithm and select the fastest.

E.g. To test relative retrieval of ArrayLists and LinkedLists. Retrieval for ArrayLists is O(1), and retrieval for LinkedLists is O(n). Retrieving an item in the 1000th position should be much faster using an ArrayList.

Step 1 - Populate the Lists

@Test
public void testFastest() throws InterruptedException, ExecutionException {

	ArrayList<Integer> arrayList = new ArrayList<>();
	LinkedList<Integer> linkedList = new LinkedList<>();
	for(int i=0;i<1001;i++){
		arrayList.add(i);
		linkedList.add(i);
	}
    }

Step 2 - Create a simple result class

We use Lombok for this type of thing, as it makes creating Immutable classes very straightforward and greatly cuts down boilerplate code.

@Wither        //adds 'withXXXX' methods that create a new instance with property XXXX changed
@Getter    //adds Java Bean getXXXX methods
@AllArgsConstructor   //adds a Constructor which accepts all fields
@Builder    //adds a builder class for this class
static class Result{
	private final String name;
	private final int result;
	private final long time;
	
}

The equivalent Java for this class is at the bottom of this example doc.

Step 3 - Define a simple retrieval method that looks up the 1000th entry in a list

private int retrieval(List<Integer> list) {
	return list.get(1000);
}

Step 4 - Setup a reactive flow with two suppliers - one for an ArrayList and one for the LinkedList

@Test
public void testFastest() throws InterruptedException, ExecutionException {

	ArrayList<Integer> arrayList = new ArrayList<>();
	LinkedList<Integer> linkedList = new LinkedList<>();
	for(int i=0;i<1001;i++){
		arrayList.add(i);
		linkedList.add(i);
	}
	
	
	Result result = new SimpleReact()
	.<Result> react( () -> Result.builder().name("approach1").result(retrieval(arrayList)).build(), 
			() -> Result.builder().name("approach2").result(retrieval(linkedList)).build())

}

Step 5 - Capture the time taken for each approach

 @Test
public void testFastest() throws InterruptedException, ExecutionException {

	ArrayList<Integer> arrayList = new ArrayList<>();
	LinkedList<Integer> linkedList = new LinkedList<>();
	for(int i=0;i<1001;i++){
		arrayList.add(i);
		linkedList.add(i);
	}
	SimpleTimer timer = new SimpleTimer();
	
	Result result = new SimpleReact()
	.<Result> react( () -> Result.builder().name("approach1").result(retrieval(arrayList)).build(), 
			() -> Result.builder().name("approach2").result(retrieval(linkedList)).build())
	.then(it -> it.withTime(timer.getElapsedNanoseconds()))

}

Step 6 - Ensure both answers are correct

 @Test
public void testFastest() throws InterruptedException, ExecutionException {

	ArrayList<Integer> arrayList = new ArrayList<>();
	LinkedList<Integer> linkedList = new LinkedList<>();
	for(int i=0;i<1001;i++){
		arrayList.add(i);
		linkedList.add(i);
	}
	SimpleTimer timer = new SimpleTimer();
	
	Result result = new SimpleReact()
	.<Result> react( () -> Result.builder().name("approach1").result(retrieval(arrayList)).build(), 
			() -> Result.builder().name("approach2").result(retrieval(linkedList)).build())
	.then(it -> it.withTime(timer.getElapsedNanoseconds()))
	.filter(it -> it.getResult()==1000

}

Step 7 - Select the first result returned and check the approach name

  @Test
public void testFastest() throws InterruptedException, ExecutionException {

	ArrayList<Integer> arrayList = new ArrayList<>();
	LinkedList<Integer> linkedList = new LinkedList<>();
	for(int i=0;i<1001;i++){
		arrayList.add(i);
		linkedList.add(i);
	}
	SimpleTimer timer = new SimpleTimer();
	
	Result result = new SimpleReact()
	.<Result> react( () -> Result.builder().name("approach1").result(retrieval(arrayList)).build(), 
			() -> Result.builder().name("approach2").result(retrieval(linkedList)).build())
	.then(it -> it.withTime(timer.getElapsedNanoseconds()))
	.filter(it -> it.getResult()==1000)
	.blockAndExtract(Extractors.first());

	
	assertThat(result.getName(),is("approach1"));
}		

approach1 : ArrayList retrieval should be the fastest and first to respond.

Step 8 - Using a more efficient blocking mechanism

We are only interested in the first result back, so we don't need to block the current thread until both results are returned. We can supply a predicate to the blockAndExtract method that unblocks when a result is returned

e.g. status -> status.getCompleted() > 1

And the whole code

@Test public void testFastestLessBlocking() throws InterruptedException, ExecutionException {

	ArrayList<Integer> arrayList = new ArrayList<>();
	LinkedList<Integer> linkedList = new LinkedList<>();
	for(int i=0;i<1001;i++){
		arrayList.add(i);
		linkedList.add(i);
	}
	SimpleTimer timer = new SimpleTimer();
	
	Result result = new SimpleReact()
	.<Result> react( () -> Result.builder().name("approach1 : arrayList").result(retrieval(arrayList)).build(), 
			() -> Result.builder().name("approach2 : linkedList").result(retrieval(linkedList)).build())
	.then(it -> it.withTime(timer.getElapsedNanoseconds()))
	.filter(it -> it.getResult()==1000)
	.blockAndExtract(Extractors.first(),status -> status.getCompleted() > 0);

}

Step 9 - A little clean up

We can replace .blockAndExtract(Extractors.first(),status -> status.getCompleted() > 0) with the equivalent but neater first() method

.blockAndExtract(Extractors.first(),status -> status.getCompleted() > 0);

can be replaced with

.first()

And the whole code

@Test
public void testFastestLessBlocking() throws InterruptedException, ExecutionException {

	ArrayList<Integer> arrayList = new ArrayList<>();
	LinkedList<Integer> linkedList = new LinkedList<>();
	for(int i=0;i<1001;i++){
		arrayList.add(i);
		linkedList.add(i);
	}
	SimpleTimer timer = new SimpleTimer();
	
	Result result = new SimpleReact()
	.<Result> react( () -> Result.builder().name("approach1 : arrayList").result(retrieval(arrayList)).build(), 
			() -> Result.builder().name("approach2 : linkedList").result(retrieval(linkedList)).build())
	.then(it -> it.withTime(timer.getElapsedNanoseconds()))
	.filter(it -> it.getResult()==1000)
	.first();

	
	assertThat(result.getName(),is("approach1 : arrayList"));
}

Equivalent Raw Java and Lombok Java Results classes

Lombok Java

@Wither
@Getter
@AllArgsConstructor
@Builder
static class Result{
	private final String name;
	private final int result;
	private final long time;
	
}

Raw Java

  @Wither
@Getter
@AllArgsConstructor
@Builder
static class Result{
	private final String name;
	private final int result;
	private final long time;
	
}

static class JavaResult {

	private final String name;
	private final int result;
	private final long time;
	
	
	
	public JavaResult(String name, int result, long time) {
		super();
		this.name = name;
		this.result = result;
		this.time = time;
	}
	
	static class Builder{
		private String name;
		private int result;
		private long time;
		
		public Builder name(String name){
			this.name = name;
			return this;
		}
		
		public Builder time(long time){
			this.time = time;
			return this;
		}
		
		public Builder result(int result){
			this.result = result;
			return this;
		}
		
		public JavaResult build(){
			return new JavaResult(name,result,time);
		}
	}
	
	public String getName() {
		return name;
	}
	public int getResult() {
		return result;
	}
	public long getTime() {
		return time;
	}
	public JavaResult withName(String name) {
		return new JavaResult.Builder().name(name).result(result).time(time).build();
	}
	public JavaResult withResult(int result) {
		return new JavaResult.Builder().name(name).result(result).time(time).build();
	}
	public JavaResult getTime(long time) {
		return new JavaResult.Builder().name(name).result(result).time(time).build();
	}
	
	
	
}
Clone this wiki locally