/* * Copyright 2015 Goldman Sachs. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.gs.collections.impl.jmh; import java.util.ArrayList; import java.util.Collections; import java.util.DoubleSummaryStatistics; import java.util.Map; import java.util.PrimitiveIterator; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.gs.collections.api.map.MapIterable; import com.gs.collections.api.map.MutableMap; import com.gs.collections.api.set.Pool; import com.gs.collections.impl.jmh.runner.AbstractJMHTestRunner; import com.gs.collections.impl.list.mutable.FastList; import com.gs.collections.impl.parallel.ParallelIterate; import com.gs.collections.impl.set.mutable.UnifiedSet; import com.gs.collections.impl.test.Verify; import org.apache.commons.lang.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; @State(Scope.Thread) @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) public class AggregateByTest extends AbstractJMHTestRunner { private static final int SIZE = 1_000_000; private static final int BATCH_SIZE = 10_000; private static final Random RANDOM = new Random(System.currentTimeMillis()); private static final PrimitiveIterator.OfInt INTS = RANDOM.ints(1, 10).iterator(); private static final PrimitiveIterator.OfDouble DOUBLES = RANDOM.doubles(1.0d, 100.0d).iterator(); private final Pool<Account> accountPool = UnifiedSet.newSet(); private final Pool<Product> productPool = UnifiedSet.newSet(); private final Pool<String> categoryPool = UnifiedSet.newSet(); private final FastList<Position> gscPositions = FastList.newWithNValues(SIZE, Position::new); private final ArrayList<Position> jdkPositions = new ArrayList<>(this.gscPositions); private ExecutorService executorService; @Before @Setup(Level.Iteration) public void setUp() { this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); this.gscPositions.shuffleThis(); Collections.shuffle(this.jdkPositions); } @After @TearDown(Level.Iteration) public void tearDown() throws InterruptedException { this.executorService.shutdownNow(); this.executorService.awaitTermination(1L, TimeUnit.SECONDS); } @Benchmark public Map<Product, DoubleSummaryStatistics> aggregateByProduct_serial_lazy_jdk() { Map<Product, DoubleSummaryStatistics> result = this.jdkPositions.stream().collect( Collectors.groupingBy( Position::getProduct, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(result); return result; } @Benchmark public Map<Product, DoubleSummaryStatistics> aggregateByProduct_serial_lazy_streams_gsc() { Map<Product, DoubleSummaryStatistics> result = this.gscPositions.stream().collect( Collectors.groupingBy( Position::getProduct, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(result); return result; } @Benchmark public Map<Account, DoubleSummaryStatistics> aggregateByAccount_serial_lazy_jdk() { Map<Account, DoubleSummaryStatistics> accountDoubleMap = this.jdkPositions.stream().collect( Collectors.groupingBy( Position::getAccount, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(accountDoubleMap); return accountDoubleMap; } @Benchmark public Map<Account, DoubleSummaryStatistics> aggregateByAccount_serial_lazy_streams_gsc() { Map<Account, DoubleSummaryStatistics> accountDoubleMap = this.gscPositions.stream().collect( Collectors.groupingBy( Position::getAccount, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(accountDoubleMap); return accountDoubleMap; } @Benchmark public Map<String, DoubleSummaryStatistics> aggregateByCategory_serial_lazy_jdk() { Map<String, DoubleSummaryStatistics> categoryDoubleMap = this.jdkPositions.stream().collect( Collectors.groupingBy( Position::getCategory, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(categoryDoubleMap); return categoryDoubleMap; } @Benchmark public Map<String, DoubleSummaryStatistics> aggregateByCategory_serial_lazy_streams_gsc() { Map<String, DoubleSummaryStatistics> categoryDoubleMap = this.gscPositions.stream().collect( Collectors.groupingBy( Position::getCategory, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(categoryDoubleMap); return categoryDoubleMap; } @Benchmark public Map<Product, DoubleSummaryStatistics> aggregateByProduct_parallel_lazy_jdk() { Map<Product, DoubleSummaryStatistics> result = this.jdkPositions.parallelStream().collect( Collectors.groupingBy( Position::getProduct, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(result); return result; } @Benchmark public Map<Product, DoubleSummaryStatistics> aggregateByProduct_parallel_lazy_streams_gsc() { Map<Product, DoubleSummaryStatistics> result = this.gscPositions.parallelStream().collect( Collectors.groupingBy( Position::getProduct, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(result); return result; } @Benchmark public Map<Account, DoubleSummaryStatistics> aggregateByAccount_parallel_lazy_jdk() { Map<Account, DoubleSummaryStatistics> result = this.jdkPositions.parallelStream().collect( Collectors.groupingBy( Position::getAccount, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(result); return result; } @Benchmark public Map<Account, DoubleSummaryStatistics> aggregateByAccount_parallel_lazy_streams_gsc() { Map<Account, DoubleSummaryStatistics> result = this.gscPositions.parallelStream().collect( Collectors.groupingBy( Position::getAccount, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(result); return result; } @Benchmark public Map<String, DoubleSummaryStatistics> aggregateByCategory_parallel_lazy_jdk() { Map<String, DoubleSummaryStatistics> result = this.jdkPositions.parallelStream().collect( Collectors.groupingBy(Position::getCategory, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(result); return result; } @Benchmark public Map<String, DoubleSummaryStatistics> aggregateByCategory_parallel_lazy_streams_gsc() { Map<String, DoubleSummaryStatistics> result = this.gscPositions.parallelStream().collect( Collectors.groupingBy(Position::getCategory, Collectors.summarizingDouble(Position::getMarketValue))); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<Product, ImmutableMarketValueStatistics> aggregateByProduct_serial_eager_gsc() { MutableMap<Product, ImmutableMarketValueStatistics> result = this.gscPositions.aggregateBy( Position::getProduct, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<Account, ImmutableMarketValueStatistics> aggregateByAccount_serial_eager_gsc() { MutableMap<Account, ImmutableMarketValueStatistics> result = this.gscPositions.aggregateBy( Position::getAccount, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<String, ImmutableMarketValueStatistics> aggregateByCategory_serial_eager_gsc() { MutableMap<String, ImmutableMarketValueStatistics> result = this.gscPositions.aggregateBy( Position::getCategory, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<Product, ImmutableMarketValueStatistics> aggregateByProduct_parallel_eager_gsc() { MutableMap<Product, ImmutableMarketValueStatistics> result = ParallelIterate.aggregateBy( this.gscPositions, Position::getProduct, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<Account, ImmutableMarketValueStatistics> aggregateByAccount_parallel_eager_gsc() { MutableMap<Account, ImmutableMarketValueStatistics> result = ParallelIterate.aggregateBy( this.gscPositions, Position::getAccount, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<String, ImmutableMarketValueStatistics> aggregateByCategory_parallel_eager_gsc() { MutableMap<String, ImmutableMarketValueStatistics> result = ParallelIterate.aggregateBy( this.gscPositions, Position::getCategory, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Benchmark public MapIterable<Product, ImmutableMarketValueStatistics> aggregateByProduct_serial_lazy_gsc() { MapIterable<Product, ImmutableMarketValueStatistics> result = this.gscPositions.asLazy().aggregateBy( Position::getProduct, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Benchmark public MapIterable<Account, ImmutableMarketValueStatistics> aggregateByAccount_serial_lazy_gsc() { MapIterable<Account, ImmutableMarketValueStatistics> result = this.gscPositions.asLazy().aggregateBy( Position::getAccount, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Benchmark public MapIterable<String, ImmutableMarketValueStatistics> aggregateByCategory_serial_lazy_gsc() { MapIterable<String, ImmutableMarketValueStatistics> result = this.gscPositions.asLazy().aggregateBy( Position::getCategory, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Benchmark public MapIterable<Product, ImmutableMarketValueStatistics> aggregateByProduct_parallel_lazy_gsc() { MapIterable<Product, ImmutableMarketValueStatistics> result = this.gscPositions.asParallel(this.executorService, BATCH_SIZE) .aggregateBy( Position::getProduct, ImmutableMarketValueStatistics::getZero, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Test public void test_aggregateByProduct_parallel_lazy_gsc() { MapIterable<Product, ImmutableMarketValueStatistics> actual = this.aggregateByProduct_parallel_lazy_gsc(); MapIterable<Product, ImmutableMarketValueStatistics> expected = this.aggregateByProduct_serial_lazy_gsc(); Assert.assertEquals(expected, expected); Verify.assertMapsEqual((Map<Product, ImmutableMarketValueStatistics>) expected, (Map<Product, ImmutableMarketValueStatistics>) actual); } @Benchmark public MapIterable<Account, ImmutableMarketValueStatistics> aggregateByAccount_parallel_lazy_gsc() { MapIterable<Account, ImmutableMarketValueStatistics> result = this.gscPositions.asParallel(this.executorService, BATCH_SIZE) .aggregateBy( Position::getAccount, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Test public void test_aggregateByAccount_parallel_lazy_gsc() { MapIterable<Account, ImmutableMarketValueStatistics> actual = this.aggregateByAccount_parallel_lazy_gsc(); MapIterable<Account, ImmutableMarketValueStatistics> expected = this.aggregateByAccount_serial_lazy_gsc(); Assert.assertEquals(expected, expected); Verify.assertMapsEqual((Map<Account, ImmutableMarketValueStatistics>) expected, (Map<Account, ImmutableMarketValueStatistics>) actual); } @Benchmark public MapIterable<String, ImmutableMarketValueStatistics> aggregateByCategory_parallel_lazy_gsc() { MapIterable<String, ImmutableMarketValueStatistics> result = this.gscPositions.asParallel(this.executorService, BATCH_SIZE) .aggregateBy( Position::getCategory, ImmutableMarketValueStatistics::new, ImmutableMarketValueStatistics::add); Assert.assertNotNull(result); return result; } @Test public void test_aggregateByCategory_parallel_lazy_gsc() { MapIterable<String, ImmutableMarketValueStatistics> actual = this.aggregateByCategory_parallel_lazy_gsc(); MapIterable<String, ImmutableMarketValueStatistics> expected = this.aggregateByCategory_serial_lazy_gsc(); Assert.assertEquals(expected, expected); Verify.assertMapsEqual((Map<String, ImmutableMarketValueStatistics>) expected, (Map<String, ImmutableMarketValueStatistics>) actual); } @Benchmark public MutableMap<Product, MarketValueStatistics> aggregateInPlaceByProduct_serial_eager_gsc() { MutableMap<Product, MarketValueStatistics> result = this.gscPositions.aggregateInPlaceBy( Position::getProduct, MarketValueStatistics::new, MarketValueStatistics::accept); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<Account, MarketValueStatistics> aggregateInPlaceByAccount_serial_eager_gsc() { MutableMap<Account, MarketValueStatistics> result = this.gscPositions.aggregateInPlaceBy( Position::getAccount, MarketValueStatistics::new, MarketValueStatistics::accept); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<String, MarketValueStatistics> aggregateInPlaceByCategory_serial_eager_gsc() { MutableMap<String, MarketValueStatistics> result = this.gscPositions.aggregateInPlaceBy( Position::getCategory, MarketValueStatistics::new, MarketValueStatistics::accept); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<Product, MarketValueStatistics> aggregateInPlaceByProduct_parallel_eager_gsc() { MutableMap<Product, MarketValueStatistics> result = ParallelIterate.aggregateInPlaceBy( this.gscPositions, Position::getProduct, MarketValueStatistics::new, MarketValueStatistics::syncAccept); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<Account, MarketValueStatistics> aggregateInPlaceByAccount_parallel_eager_gsc() { MutableMap<Account, MarketValueStatistics> result = ParallelIterate.aggregateInPlaceBy( this.gscPositions, Position::getAccount, MarketValueStatistics::new, MarketValueStatistics::syncAccept); Assert.assertNotNull(result); return result; } @Benchmark public MutableMap<String, MarketValueStatistics> aggregateInPlaceByCategory_parallel_eager_gsc() { MutableMap<String, MarketValueStatistics> result = ParallelIterate.aggregateInPlaceBy( this.gscPositions, Position::getCategory, MarketValueStatistics::new, MarketValueStatistics::syncAccept); Assert.assertNotNull(result); return result; } @Benchmark public MapIterable<Product, MarketValueStatistics> aggregateInPlaceByProduct_parallel_lazy_gsc() { MapIterable<Product, MarketValueStatistics> result = this.gscPositions.asParallel(this.executorService, BATCH_SIZE) .aggregateInPlaceBy( Position::getProduct, MarketValueStatistics::new, MarketValueStatistics::syncAccept); Assert.assertNotNull(result); return result; } @Test public void test_aggregateInPlaceByProduct_parallel_lazy_gsc() { MapIterable<Product, MarketValueStatistics> actual = this.aggregateInPlaceByProduct_parallel_lazy_gsc(); MapIterable<Product, MarketValueStatistics> expected = this.aggregateInPlaceByProduct_serial_eager_gsc(); Assert.assertEquals(expected, expected); Verify.assertMapsEqual((Map<Product, MarketValueStatistics>) expected, (Map<Product, MarketValueStatistics>) actual); } @Benchmark public MapIterable<Account, MarketValueStatistics> aggregateInPlaceByAccount_parallel_lazy_gsc() { MapIterable<Account, MarketValueStatistics> result = this.gscPositions.asParallel(this.executorService, BATCH_SIZE) .aggregateInPlaceBy( Position::getAccount, MarketValueStatistics::new, MarketValueStatistics::syncAccept); Assert.assertNotNull(result); return result; } @Test public void test_aggregateInPlaceByAccount_parallel_lazy_gsc() { MapIterable<Account, MarketValueStatistics> actual = this.aggregateInPlaceByAccount_parallel_lazy_gsc(); MapIterable<Account, MarketValueStatistics> expected = this.aggregateInPlaceByAccount_serial_eager_gsc(); Assert.assertEquals(expected, expected); Verify.assertMapsEqual((Map<Account, MarketValueStatistics>) expected, (Map<Account, MarketValueStatistics>) actual); } @Benchmark public MapIterable<String, MarketValueStatistics> aggregateInPlaceByCategory_parallel_lazy_gsc() { MapIterable<String, MarketValueStatistics> result = this.gscPositions.asParallel(this.executorService, BATCH_SIZE) .aggregateInPlaceBy( Position::getCategory, MarketValueStatistics::new, MarketValueStatistics::syncAccept); Assert.assertNotNull(result); return result; } @Test public void test_aggregateInPlaceByCategory_parallel_lazy_gsc() { MapIterable<String, MarketValueStatistics> actual = this.aggregateInPlaceByCategory_parallel_lazy_gsc(); MapIterable<String, MarketValueStatistics> expected = this.aggregateInPlaceByCategory_serial_eager_gsc(); Assert.assertEquals(expected, expected); Verify.assertMapsEqual((Map<String, MarketValueStatistics>) expected, (Map<String, MarketValueStatistics>) actual); } private static boolean isCloseTo(double a, double b, double delta) { return a - b < delta || b - a < delta; } private static final class ImmutableMarketValueStatistics { private static final ImmutableMarketValueStatistics ZERO = new ImmutableMarketValueStatistics(); private final long count; private final double sum; private final double min; private final double max; private ImmutableMarketValueStatistics() { this(0, 0.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY); } private ImmutableMarketValueStatistics(long count, double sum, double min, double max) { this.count = count; this.sum = sum; this.min = min; this.max = max; } public ImmutableMarketValueStatistics add(Position position) { double marketValue = position.getMarketValue(); return new ImmutableMarketValueStatistics( this.count + 1, this.sum + marketValue, Math.min(this.min, marketValue), Math.max(this.max, marketValue)); } public static ImmutableMarketValueStatistics getZero() { return ZERO; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || this.getClass() != o.getClass()) { return false; } ImmutableMarketValueStatistics that = (ImmutableMarketValueStatistics) o; if (this.count != that.count) { return false; } if (Double.compare(that.max, this.max) != 0) { return false; } if (Double.compare(that.min, this.min) != 0) { return false; } return AggregateByTest.isCloseTo(that.sum, this.sum, 0.0001); } @Override public int hashCode() { int result = (int) (this.count ^ (this.count >>> 32)); long temp = Double.doubleToLongBits(this.sum); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(this.min); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(this.max); result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public String toString() { return "ImmutableMarketValueStatistics{" + "count=" + this.count + ", sum=" + this.sum + ", min=" + this.min + ", max=" + this.max + '}'; } } private static final class MarketValueStatistics extends DoubleSummaryStatistics { public void accept(Position position) { this.accept(position.getMarketValue()); } public synchronized void syncAccept(Position position) { this.accept(position); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || this.getClass() != o.getClass()) { return false; } MarketValueStatistics that = (MarketValueStatistics) o; if (this.getCount() != that.getCount()) { return false; } if (Double.compare(that.getMax(), this.getMax()) != 0) { return false; } if (Double.compare(that.getMin(), this.getMin()) != 0) { return false; } return AggregateByTest.isCloseTo(that.getSum(), this.getSum(), 0.01); } @Override public int hashCode() { int result = (int) (this.getCount() ^ (this.getCount() >>> 32)); long temp = Double.doubleToLongBits(this.getSum()); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(this.getMin()); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(this.getMax()); result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } } private final class Position { private final Account account = AggregateByTest.this.accountPool.put(new Account()); private final Product product = AggregateByTest.this.productPool.put(new Product()); private final int quantity = INTS.nextInt(); public Account getAccount() { return this.account; } public Product getProduct() { return this.product; } public String getCategory() { return this.product.getCategory(); } public int getQuantity() { return this.quantity; } public double getMarketValue() { return this.quantity * this.product.getPrice(); } } private static final class Account { private final String name = RandomStringUtils.randomNumeric(5); public String getName() { return this.name; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || this.getClass() != o.getClass()) { return false; } Account account = (Account) o; return this.name.equals(account.name); } @Override public int hashCode() { return this.name.hashCode(); } } private final class Product { private final String name = RandomStringUtils.randomNumeric(3); private final String category = AggregateByTest.this.categoryPool.put(RandomStringUtils.randomAlphabetic(1).toUpperCase()); private final double price = DOUBLES.nextDouble(); public String getName() { return this.name; } public double getPrice() { return this.price; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || this.getClass() != o.getClass()) { return false; } Product account = (Product) o; return this.name.equals(account.name); } public String getCategory() { return this.category; } @Override public int hashCode() { return this.name.hashCode(); } @Override public String toString() { return "Product{" + "name='" + this.name + '\'' + ", category='" + this.category + '\'' + ", price=" + this.price + '}'; } } }