2014-08-29 42 views
3

我有了(除其他事项外)类:TreeMap的过滤视图性能

public class TimeSeries { 
    private final NavigableMap<LocalDate, Double> prices; 

    public TimeSeries() { prices = new TreeMap<>(); } 

    private TimeSeries(NavigableMap<LocalDate, Double> prices) { 
    this.prices = prices; 
    } 

    public void add(LocalDate date, double price) { prices.put(date, price); } 

    public Set<LocalDate> dates() { return prices.keySet(); } 

    //the 2 methods below are examples of why I need a TreeMap 

    public double lastPriceAt(LocalDate date) { 
    Map.Entry<LocalDate, Double> price = prices.floorEntry(date); 
    return price.getValue(); //after some null checks 
    } 

    public TimeSeries between(LocalDate from, LocalDate to) { 
    return new TimeSeries(this.prices.subMap(from, true, to, true)); 
    } 
} 

现在我需要的地图,只有部分日期可在“过滤”的看法。该效果我已经添加了以下方法:

public TimeSeries onDates(Set<LocalDate> retainDates) { 
    TimeSeries filtered = new TimeSeries(new TreeMap<> (this.prices)); 
    filtered.dates().retainAll(retainDates); 
    return filtered; 
} 

onDates方法是一个巨大的性能瓶颈,代表的程序的处理时间的85%。由于该程序正在运行数百万次的模拟,这意味着花费数小时的时间。

我该如何提高该方法的性能?

+0

过滤的'TimeSeries'副本将迭代多少次? 'retainDates'通常比'prices'小很多? – biziclop 2014-08-29 17:55:35

+0

@biziclop地图通常包含1500个条目,并且该集合将具有几乎相同的大小(可能具有相同的大小,包含相同的日期)。过滤的TimeSeries通常只用于(迭代)一次。 – assylias 2014-08-29 17:58:15

+0

我对LocalDate并不太熟悉,但仅仅通过执行prices.get(localeDate)就可以获得想要的值? – coffeeaddict 2014-08-29 18:00:16

回答

1

我想给ImmutableSortedMap一个尝试,假设你可以使用它。它基于排序数组而非平衡树,所以我猜它的开销要小得多(*)。为了建造它,你需要使用biziclop的想法,因为建造者不支持清除。 (*)Collection.sort这里有一个电话,但它应该是无害的,因为收集已经排序,TimSort针对这种情况进行了优化。


如果你原来的地图生成onDates后不发生变化,也许一个视图可帮助。如果是这样,你需要一些“持久”的地图,这听起来相当复杂。

也许基于排序数组和二进制搜索一些哈克的解决方案可能是最快的,也许你甚至可以转换LocalDateint再到double,把一切都变成一个单一的交错double[]为了节省内存(希望也时刻)。你需要自己的二进制搜索,但这是相当微不足道的。


的观点想法很简单,假设

  • 你并不需要所有NavigableMap方法,但只是一对夫妇的方法
  • 原始地图不改变
  • retainDates

示例方法:

public double lastPriceAt(LocalDate date) { 
    Map.Entry<LocalDate, Double> price = prices.floorEntry(date); 
    while (!retainDates.contains(price.getKey()) { 
     price = prices.lowerEntry(price.getKey()); // after some null checks 
    } 
    return price.getValue(); // after some null checks 
} 
+0

这是一个旧版本,无法导航 - [最新的](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/ImmutableSortedMap.html)是和我'我会试试看。感谢这个想法。 – assylias 2014-08-30 06:49:31

+0

我在retainDates中运行了包含1500个数据点和3个缺少日期的微型基准测试:我的代码需要50微秒,而您的提议需要80微秒。所以你的建议确实会显着提高biziclop的代码,但仍然比原始版本慢。不管怎么说,还是要谢谢你! – assylias 2014-08-30 07:30:22

+0

经过更多测试后,它看起来像唯一的方法是使用'LocalDate []日期;'和'double []价格;'并且使用binarySearch作为'lastPriceAt'。使用阵列可将性能提高约3倍。 – assylias 2014-08-30 08:55:54

1

最简单的优化:

public TimeSeries onDates(Set<LocalDate> retainDates) { 
    TreeMap<LocalDate, Double> filteredPrices = new TreeMap<>(); 
    for (Entry<LocalDate, Double> entry : prices.entrySet()) { 
     if (retainDates.contains(entry.getKey())) { 
      filteredPrices.put(entry.getKey(), entry.getValue()); 
     } 
    } 
    TimeSeries filtered = new TimeSeries(filteredPrices); 
    return filtered; 
} 

节省您先创建地图的完整拷贝的成本,然后在副本迭代再次进行筛选。

+0

谢谢,我明天会测试它。 TreeMap的拷贝构造函数经过了优化,所以我不知道这是否会更快。然后TBC! – assylias 2014-08-29 18:55:28

+1

构造函数已经过优化,但'retainAll()'不是:它只是在整个地图上盲目迭代。虽然保留时可能需要更少的树重新平衡。这是值得一试的,我不是说它肯定会奏效。 – biziclop 2014-08-29 19:02:43

+0

我在retainDates中运行了包含1500个数据点和3个缺失日期的微基准测试:我的代码需要50微秒,而您的提议需要200微秒。不管怎么说,还是要谢谢你! – assylias 2014-08-30 07:29:53