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;
}
}