package examples.portfolio;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.lang3.mutable.MutableDouble;
import org.junit.Test;

import net.openhft.chronicle.core.values.LongValue;
import net.openhft.chronicle.map.ChronicleMap;
import net.openhft.chronicle.map.ChronicleMapBuilder;
import net.openhft.chronicle.map.MapSegmentContext;
import net.openhft.chronicle.values.Values;

public class PortfolioValueTest
{
	private static final boolean useIterator = true;
	private static final long nAssets = 10_000_000;
	private static final int nThreads = Runtime.getRuntime().availableProcessors();
	private static final int nRepetitions = 11; // 10 for computing average, throwing away the first one (warmup)

	@Test
	public void test() throws ExecutionException, InterruptedException
	{
		ChronicleMapBuilder<LongValue, PortfolioAssetInterface> mapBuilder = ChronicleMapBuilder.of(LongValue.class, PortfolioAssetInterface.class).entries(nAssets);

		try (ChronicleMap<LongValue, PortfolioAssetInterface> cache = mapBuilder.create())
		{
			createData(cache);

			// Compute multiple times to get an reasonable average compute time
			for (int i = 0; i < nRepetitions; i++)
			{
				computeValue(cache);
			}
		}
	}

	private void createData(final ChronicleMap<LongValue, PortfolioAssetInterface> cache) throws ExecutionException, InterruptedException
	{
		long startTime = System.currentTimeMillis();

		ExecutorService executor = Executors.newFixedThreadPool(nThreads);
		Future<?>[] futures = new Future[nThreads];
		long batchSize = nAssets / nThreads;

		for (int t = 0; t < nThreads; t++)
		{
			final long batch = t;
			futures[t] = executor.submit(() -> {
				final LongValue key = Values.newHeapInstance(LongValue.class);
				final PortfolioAssetInterface value = Values.newHeapInstance(PortfolioAssetInterface.class);

				long start = batch * batchSize;
				long end = Math.min(nAssets, (batch + 1) * batchSize);
				long n = (end - start);

				if (end > start)
				{
					System.out.println("Inserting batch " + (batch + 1) + "/" + nThreads + " of " + n + " records");

					for (long k = start; k < end; k++)
					{
						key.setValue(k);
						value.setAssetId(k);
						value.setShares(1);
						value.setPrice(2.0);
						cache.put(key, value);
					}
				}
			});
		}

		for (Future<?> future : futures)
		{
			future.get();
		}

		long elapsedTime = (System.currentTimeMillis() - startTime);
		System.out.println("Data inserted in " + elapsedTime + " ms");
	}

	private static void computeValue(final ChronicleMap<LongValue, PortfolioAssetInterface> cache) throws ExecutionException, InterruptedException
	{
		computeValueUsingIterator(cache);
	}

	private static void computeValueUsingIterator(final ChronicleMap<LongValue, PortfolioAssetInterface> cache) throws ExecutionException, InterruptedException
	{
		long startTime = System.currentTimeMillis();

		ExecutorService executor = Executors.newFixedThreadPool(nThreads);
		@SuppressWarnings("unchecked")
		Future<Double>[] futures = new Future[nThreads];
		long batchSize = (useIterator ? cache.segments() : nAssets) / nThreads;

		// Map
		for (int t = 0; t < nThreads; t++)
		{
			final int batch = t;

			futures[t] = executor.submit(() -> {
				if (useIterator)
				{
					int start = (int) (batch * batchSize);
					int end = (int) Math.min(cache.segments(), (batch + 1) * batchSize);
					return computeTotalUsingIterator(cache, start, end);
				}
				else
				{
					long start = batch * batchSize;
					long end = Math.min(nAssets, (batch + 1) * batchSize);
					return computeTotalUsingKeys(cache, start, end);
				}
			});
		}

		// Reduce
		double total = 0;
		for (Future<Double> future : futures)
		{
			total += future.get();
		}

		long elapsedTime = (System.currentTimeMillis() - startTime);
		System.out.println("Total Portfolio Value: " + total + " for " + cache.longSize() + ", computed in " + elapsedTime + " ms, using " + (useIterator ? "Iterator" : "Keys"));
	}

	protected static double computeTotalUsingIterator(final ChronicleMap<LongValue, PortfolioAssetInterface> cache, int start, int end)
	{
		if (end > start)
		{
			final PortfolioAssetInterface asset = Values.newHeapInstance(PortfolioAssetInterface.class);
			PortfolioValueAccumulator accumulator = new PortfolioValueAccumulator(new MutableDouble(), asset);
			for (int s = start; s < end; s++)
			{
				try (MapSegmentContext<LongValue, PortfolioAssetInterface, ?> context = cache.segmentContext(s))
				{
					context.forEachSegmentEntry(accumulator);
				}
			}
			return accumulator.total.doubleValue();
		}
		return 0;
	}

	protected static double computeTotalUsingKeys(final ChronicleMap<LongValue, PortfolioAssetInterface> cache, long start, long end)
	{
		final LongValue key = Values.newHeapInstance(LongValue.class);
		PortfolioAssetInterface asset = Values.newHeapInstance(PortfolioAssetInterface.class);

		double total = 0;
		for (long k = start; k < end; k++)
		{
			key.setValue(k);
			asset = cache.getUsing(key, asset);
			total += asset.getShares() * asset.getPrice();
		}
		return total;
	}
}

Create PortfolioValueTest.java