mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Add caches to TemporalAdjusterModel to speed up BSQ dashboard view
Now that the trade statistics are retrieved in chronological order, optimise the per-interval BSQ & USD price and volume calculations in PriceChartDataModel & VolumeChartDataModel, by adding caches to avoid relatively expensive timezone calculations in TemporalAdjusterModel, similarly to the cache added for 'ChartCalculations.roundToTick' (as profiling shows 'TemporalAdjusterModel.toTimeInteval' is a hotspot). Add a cache to speed up Instant -> LocalDate mappings by storing the unix time (Instant) range of the last seen day (LocalDate) in a tuple, then just returning that day if the next Instant falls in range. Also add a cache of the last temporal adjustment (start of month, week, etc.) of that day. In this way, successive calls to 'toTimeInteval(Instant)' with input times on the same day are sped up. Since TemporalAdjusterModel is used by multiple threads simultaneously, store the caches in instance fields and add a 'withCache' method which clones the model and enables the caching, since otherwise the separate threads keep invalidating one another's caches, making it slower than it would be without them. (We could use ThreadLocals, but profiling suggests they are too heavyweight to be very useful here, so instead use unsynchronised caching with nonfinal fields and benign data races.) Provide the method 'ChartDataModel.toCachedTimeIntervalFn' which returns a method reference to a cloned & cache-enabled TemporalAdjustedModel, to use in place of the delegate method 'ChartDataModel.toTimeInterval' when the caching is beneficial.
This commit is contained in:
parent
964321a1e1
commit
f3fd555ced
@ -22,10 +22,10 @@ import bisq.desktop.common.model.ActivatableDataModel;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.TemporalAdjuster;
|
import java.time.temporal.TemporalAdjuster;
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BinaryOperator;
|
import java.util.function.BinaryOperator;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.ToLongFunction;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@ -64,6 +64,11 @@ public abstract class ChartDataModel extends ActivatableDataModel {
|
|||||||
return temporalAdjusterModel.toTimeInterval(instant);
|
return temporalAdjusterModel.toTimeInterval(instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// optimized for use when the input times are sequential and not too spread out
|
||||||
|
public ToLongFunction<Instant> toCachedTimeIntervalFn() {
|
||||||
|
return temporalAdjusterModel.withCache()::toTimeInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Date filter predicate
|
// Date filter predicate
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package bisq.desktop.components.chart;
|
package bisq.desktop.components.chart;
|
||||||
|
|
||||||
import bisq.common.util.MathUtils;
|
import bisq.common.util.MathUtils;
|
||||||
|
import bisq.common.util.Tuple3;
|
||||||
|
|
||||||
import java.time.DayOfWeek;
|
import java.time.DayOfWeek;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@ -37,6 +38,19 @@ import static java.time.temporal.ChronoField.DAY_OF_YEAR;
|
|||||||
public class TemporalAdjusterModel {
|
public class TemporalAdjusterModel {
|
||||||
private static final ZoneId ZONE_ID = ZoneId.systemDefault();
|
private static final ZoneId ZONE_ID = ZoneId.systemDefault();
|
||||||
|
|
||||||
|
protected TemporalAdjuster temporalAdjuster = Interval.MONTH.getAdjuster();
|
||||||
|
|
||||||
|
private boolean enableCache;
|
||||||
|
private Tuple3<LocalDate, Instant, Instant> cachedDateStartEndTuple;
|
||||||
|
private Tuple3<LocalDate, TemporalAdjuster, Long> cachedTimeIntervalMapping;
|
||||||
|
|
||||||
|
public TemporalAdjusterModel withCache() {
|
||||||
|
var model = new TemporalAdjusterModel();
|
||||||
|
model.temporalAdjuster = this.temporalAdjuster;
|
||||||
|
model.enableCache = true;
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
public enum Interval {
|
public enum Interval {
|
||||||
YEAR(TemporalAdjusters.firstDayOfYear()),
|
YEAR(TemporalAdjusters.firstDayOfYear()),
|
||||||
HALF_YEAR(temporal -> {
|
HALF_YEAR(temporal -> {
|
||||||
@ -81,8 +95,6 @@ public class TemporalAdjusterModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TemporalAdjuster temporalAdjuster = Interval.MONTH.getAdjuster();
|
|
||||||
|
|
||||||
public void setTemporalAdjuster(TemporalAdjuster temporalAdjuster) {
|
public void setTemporalAdjuster(TemporalAdjuster temporalAdjuster) {
|
||||||
this.temporalAdjuster = temporalAdjuster;
|
this.temporalAdjuster = temporalAdjuster;
|
||||||
}
|
}
|
||||||
@ -96,12 +108,36 @@ public class TemporalAdjusterModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long toTimeInterval(Instant instant, TemporalAdjuster temporalAdjuster) {
|
public long toTimeInterval(Instant instant, TemporalAdjuster temporalAdjuster) {
|
||||||
return instant
|
LocalDate date = toLocalDate(instant);
|
||||||
.atZone(ZONE_ID)
|
Tuple3<LocalDate, TemporalAdjuster, Long> tuple = cachedTimeIntervalMapping;
|
||||||
.toLocalDate()
|
long timeInterval;
|
||||||
|
if (tuple != null && date.equals(tuple.first) && temporalAdjuster.equals(tuple.second)) {
|
||||||
|
timeInterval = tuple.third;
|
||||||
|
} else {
|
||||||
|
timeInterval = date
|
||||||
.with(temporalAdjuster)
|
.with(temporalAdjuster)
|
||||||
.atStartOfDay(ZONE_ID)
|
.atStartOfDay(ZONE_ID)
|
||||||
.toInstant()
|
.toEpochSecond();
|
||||||
.getEpochSecond();
|
if (enableCache) {
|
||||||
|
cachedTimeIntervalMapping = new Tuple3<>(date, temporalAdjuster, timeInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timeInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDate toLocalDate(Instant instant) {
|
||||||
|
LocalDate date;
|
||||||
|
Tuple3<LocalDate, Instant, Instant> tuple = cachedDateStartEndTuple;
|
||||||
|
if (tuple != null && !instant.isBefore(tuple.second) && instant.isBefore(tuple.third)) {
|
||||||
|
date = tuple.first;
|
||||||
|
} else {
|
||||||
|
date = instant.atZone(ZONE_ID).toLocalDate();
|
||||||
|
if (enableCache) {
|
||||||
|
cachedDateStartEndTuple = new Tuple3<>(date,
|
||||||
|
date.atStartOfDay(ZONE_ID).toInstant(),
|
||||||
|
date.plusDays(1).atStartOfDay(ZONE_ID).toInstant());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,9 +188,10 @@ public class PriceChartDataModel extends ChartDataModel {
|
|||||||
|
|
||||||
private Map<Long, Double> getPriceByInterval(Predicate<TradeStatistics3> collectionFilter,
|
private Map<Long, Double> getPriceByInterval(Predicate<TradeStatistics3> collectionFilter,
|
||||||
Function<List<TradeStatistics3>, Double> getAveragePriceFunction) {
|
Function<List<TradeStatistics3>, Double> getAveragePriceFunction) {
|
||||||
|
var toTimeIntervalFn = toCachedTimeIntervalFn();
|
||||||
return getPriceByInterval(tradeStatisticsManager.getObservableTradeStatisticsSet(),
|
return getPriceByInterval(tradeStatisticsManager.getObservableTradeStatisticsSet(),
|
||||||
collectionFilter,
|
collectionFilter,
|
||||||
tradeStatistics -> toTimeInterval(Instant.ofEpochMilli(tradeStatistics.getDateAsLong())),
|
tradeStatistics -> toTimeIntervalFn.applyAsLong(Instant.ofEpochMilli(tradeStatistics.getDateAsLong())),
|
||||||
dateFilter,
|
dateFilter,
|
||||||
getAveragePriceFunction);
|
getAveragePriceFunction);
|
||||||
}
|
}
|
||||||
|
@ -134,8 +134,9 @@ public class VolumeChartDataModel extends ChartDataModel {
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private Map<Long, Long> getVolumeByInterval(Function<List<TradeStatistics3>, Long> getVolumeFunction) {
|
private Map<Long, Long> getVolumeByInterval(Function<List<TradeStatistics3>, Long> getVolumeFunction) {
|
||||||
|
var toTimeIntervalFn = toCachedTimeIntervalFn();
|
||||||
return getVolumeByInterval(tradeStatisticsManager.getObservableTradeStatisticsSet(),
|
return getVolumeByInterval(tradeStatisticsManager.getObservableTradeStatisticsSet(),
|
||||||
tradeStatistics -> toTimeInterval(Instant.ofEpochMilli(tradeStatistics.getDateAsLong())),
|
tradeStatistics -> toTimeIntervalFn.applyAsLong(Instant.ofEpochMilli(tradeStatistics.getDateAsLong())),
|
||||||
dateFilter,
|
dateFilter,
|
||||||
getVolumeFunction);
|
getVolumeFunction);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user