My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
PerformanceAnalysis  
Compares performances of same algorithms implemented with and without lambdaj
Updated Jan 16, 2011 by mario.fu...@gmail.com

This table show the minimum, maximum and average duration in milliseconds of 20 runs of 100,000 iterations of the examples below. These examples are referred to a very simple data model made of some sales of cars between two persons, a seller and a buyer. They show a comparison of how the same results can be achieved in the usual iterative java style and by using lambdaj. These figures are referred to the last 2.3.2 release

iterative lambdaj
Test name min max avg min max avg ratio
Print all brands 265 312 283 1,310 1,591 1,377 4.866
Select all sales of a Ferrari 281 437 366 1,528 1,607 1,566 4.279
Find buys of youngest person 5,585 5,975 5,938 6,895 6,989 6,936 1.168
Find most costly sale 218 234 227 655 702 670 2.952
Sum costs where both are males 358 452 375 2,199 2,637 2,247 5.592
Age of youngest who bought for > 50,000 5,257 5,319 5,292 9,625 9,750 9,696 1.832
Sort sales by cost 1,388 1,482 1,448 3,213 3,245 3,231 2.231
Extract cars original cost 140 156 141 234 249 236 1.674
Index cars by brand 172 203 186 327 343 336 1.806
Group sales by buyers and sellers 9,469 9,766 9,507 12,698 12,838 12,753 1.341
Find most bought car 3,744 3,884 3,846 4,181 4,259 4,211 1.095

Average ratio = 2.658

Print all brands

It concatenates, in a comma separated String, all the cars' brands

iterative version:

StringBuilder sb = new StringBuilder();
for (Car car : db.getCars()) 
    sb.append(car.getBrand()).append(", ");
String brands = sb.toString().substring(0, sb.length()-2);

lambdaj version:

String brands = joinFrom(db.getCars()).getBrand();

Select all sales of a Ferrari

It filters all the sales of a car having the brand equals to "Ferrari"

iterative version:

List<Sale> salesOfAFerrari = new ArrayList<Sale>();
for (Sale sale : sales) {
    if (sale.getCar().getBrand().equals("Ferrari")) 
        salesOfAFerrari.add(sale);
}

lambdaj version:

List<Sale> salesOfAFerrari = select(sales,
    having(on(Sale.class).getCar().getBrand(),equalTo("Ferrari")));

Find buys of youngest person

It finds the youngest person and then filters all the sales where that person was the buyer

iterative version:

Person youngest = null;
for (Person person : persons)
    if (youngest == null || person.getAge() < youngest.getAge())
        youngest = person;
List<Sale> buys = new ArrayList<Sale>();
for (Sale sale : sales)
    if (sale.getBuyer().equals(youngest)) 
        buys.add(sale);

lambdaj version:

List<Sale> sales = select(sales,having(on(Sale.class).getBuyer(),
    equalTo(selectMin(persons, on(Person.class).getAge()))));

Find most costly sale

iterative version:

double maxCost = 0.0;
for (Sale sale : sales) {
    double cost = sale.getCost();
    if (cost > maxCost) maxCost = cost;
}

lambdaj version:

Sol. 1 -> double maxCost = max(sales, on(Sale.class).getCost());
Sol. 2 -> double maxCost = maxFrom(sales).getCost();

Sum costs where both are males

It sums up the values of all the sales where both the buyer and the seller are males

iterative version:

double sum = 0.0;
for (Sale sale : sales) {
    if (sale.getBuyer().isMale() && sale.getSeller().isMale())
        sum += sale.getCost();
}

lambdaj version:

double sum = sumFrom(select(sales, 
    having(on(Sale.class).getBuyer().isMale())
    .and( having(on(Sale.class).getSeller().isMale())))).getCost();

Find age of youngest who bought for more than 50000

iterative version:

int age = Integer.MAX_VALUE;
for (Sale sale : sales) {
    if (sale.getCost() > 50000.00) {
        int buyerAge = sale.getBuyer().getAge();
        if (buyerAge < age) age = buyerAge;
    }
}

lambdaj version:

int age = min(forEach(select(sales, having(on(Sale.class).getCost(),
    greaterThan(50000.00)))).getBuyer(), on(Person.class).getAge());

Sort sales by cost

iterative version:

List<Sale> sortedSales = new ArrayList<Sale>(sales);
Collections.sort(sortedSales, new Comparator<Sale>() {
    public int compare(Sale s1, Sale s2) {
        return Double.valueOf(s1.getCost()).compareTo(s2.getCost());
    }
});

lambdaj version:

List<Sale> sortedSales = sort(sales, on(Sale.class).getCost());

Extract cars original cost

iterative version:

List<Double> costs = new ArrayList<Double>();
for (Car car : cars) costs.add(car.getOriginalValue());

lambdaj version:

List<Double> costs = extract(cars, on(Car.class).getOriginalValue());

Index cars by brand

iterative version:

Map<String, Car> carsByBrand = new HashMap<String, Car>();
for (Car car : db.getCars()) 
    carsByBrand.put(car.getBrand(), car);

lambdaj version:

Map<String, Car> carsByBrand = index(cars, on(Car.class).getBrand());

Group sales by buyers and sellers

iterative version:

Map<Person,Map<Person,Sale>> map = new HashMap<Person,Map<Person,Sale>>();
for (Sale sale : sales) {
    Person buyer = sale.getBuyer();
    Map<Person, Sale> buyerMap = map.get(buyer);
    if (buyerMap == null) {
        buyerMap = new HashMap<Person, Sale>();
        map.put(buyer, buyerMap);
    }
    buyerMap.put(sale.getSeller(), sale);
}
Person youngest = null;
Person oldest = null;
for (Person person : persons) {
    if (youngest == null || person.getAge() < youngest.getAge())
        youngest = person;
    if (oldest == null || person.getAge() > oldest.getAge()) 
        oldest = person;
}
Sale saleFromYoungestToOldest = map.get(youngest).get(oldest);

lambdaj version:

Group<Sale> group = group(sales, by(on(Sale.class).getBuyer()),by(on(Sale.class).getSeller()));
Person youngest = selectMin(persons, on(Person.class).getAge());
Person oldest = selectMax(persons, on(Person.class).getAge());
Sale sale = group.findGroup(youngest).find(oldest).get(0);

Find most bought car

It finds the car that has been bought more times

iterative version:

Map<Car, Integer> carsBought = new HashMap<Car, Integer>();
for (Sale sale : sales) {
    Car car = sale.getCar();
    Integer boughtTimes = carsBought.get(car);
    carsBought.put(car, boughtTimes == null ? 1 : boughtTimes+1);
}

Car mostBoughtCarIterative = null;
int boughtTimesIterative = 0;
for (Entry<Car, Integer> entry : carsBought.entrySet()) {
    if (entry.getValue() > boughtTimesIterative) {
        mostBoughtCarIterative = entry.getKey();
        boughtTimesIterative = entry.getValue();
    }
}

lambdaj version:

Group<Sale> group = selectMax(
    group(sales, by(on(Sale.class).getCar())).subgroups(), on(Group.class).getSize());
Car mostBoughtCar = group.findAll().get(0).getCar();
int boughtTimes = group.getSize();
Comment by marc...@gmail.com, Aug 28, 2010

Am I understanding this correctly? The performance penalty hit ratio for using LambdaJ is an average 3? 3 times slower than iterative versions?

Comment by project member mario.fu...@gmail.com, Aug 28, 2010

Yes you are understanding well. This performance penalty is caused by the huge use of dynamic proxy and reflection made by lambdaj.

However, in my experience, it happens very seldom that the overhead introduced by lambdaj becomes application's bottleneck. For example, if you fetch your objects from a database, the performance loss caused by lambdaj is orders of magnitude lesser than that fetch operation.

In the end lambdaj is not the only library to work in this way. There are a lot of frameworks out there (including Spring and Hibernate) that do exactly the same and very few people complain about their performance issues. Probably because the benefits they bring widely overcome the small performance loss they introduce. And I hope the same applies for lambdaj.

So my advice is to give a chance to lambdaj, measure the differences in performance with or without it and figure out if the performance loss is acceptable for you or not.

Comment by vladimir.orany, Oct 2, 2010

could you, please, add also groovy into the performance analysis? the examples' code should look like this

def brands = db.getCars().collect{it.brand}.join(',')

def salesOfAFerrari = sales.findAll{it.car.brand == 'Ferrari'}

def buys = sales.findAll{ it.buyer.age == persons*.age.min() }

def maxCost = sales*.cost.max()

def sum = sales.findAll{ it.buyer.male && it.seller.male }*.cost.sum()

def age = sales.findAll{ it.cost > 50000 }*.buyer.age.min()

def sortedSales = sales.sort{ it.cost }

def costs = cars*.originalValue

def carsByBrand = cars.groupBy{ it.brand }

def minAge = persons*.age.min()
def maxAge = persons*.age.max()
def sales = sales.findAll{ it.seller.age == minAge && it.buyer.age = maxAge }

def salesByCar= sales.groupBy{ it.car }.sort{ a, b -> a.value.size() <=> b.value.size() }
def mostBoughtCar = salesByCar.collect{ key,value -> key }.last()
def boughtTime = salesByCar.collect{ key,value -> value}.last().size()
Comment by project member mario.fu...@gmail.com, Oct 2, 2010

I am sorry but I believe the comparison you are suggesting doesn't make any sense. I never claimed neither thought that lambdaj could be a replacement for a programming language that natively supports functional programming. If you can go with Groovy (or even better Scala, I personally hate dynamic typing) go with it and forget lambdaj. Lambdaj is intended to be used by people, like me, who likes FP but are obliged to stuck to Java in their day by day work. Does it make sense?

Comment by derar...@gmail.com, Dec 20, 2010

How many cars, sales and persons did you have in your test data? Maybe you could also do the tests with different data amounts to show how it scales (this is actually more important than the concrete overhead).

Comment by brice.du...@gmail.com, Mar 7, 2011

I second that last comment. How does it scale, vertically and horizontally?

Comment by decho...@gmail.com, Jun 26, 2011

If I am not mistaken, in the case that LambdaJ should be used only with a limited number of classes, the runtime overhead of dynamic proxy and reflection could be avoided by generating classes like for QueryDSL/JPA : http://blog.mysema.com/2010/04/querydsl-as-alternative-to-jpa-2.html.

I would love to have your opinion on this. Is this something which is on the roadmap?

Comment by costcoch...@gmail.com, Dec 22, 2011

Great to see some performance analysis up front to set expectations. Although I have not ventured over to download the source code yet, does it include this sample code above?

Suggestion to provide the above code in a completely separate download package so users can take it and perform analysis on their own systems.

Thank you for all your hard work. Cheers.

Comment by erwan.le...@gmail.com, Dec 13, 2012

Can you explain what you mean by duration in milliseconds of 20 runs of 100,000 iterations? Do you have an example of what you do exactly to have your result ? Can you also explain why you have a ratio of 4.866 when lambdaj is quicker than iterative and something superior to 1 when iterative is quicker than lambdaj ? If it is also same formula for your ratio, you should have something superior to 1 in one case, something equals to 1 if they have same result, and something inferior to 1 otherwise. Your ideas and developpment (very good job) look very interesting, I just hope that you don't cheat on your performance results in order to obtain a good feeling with it.


Sign in to add a comment
Powered by Google Project Hosting