RangeBitmap提升Java流資料過濾效能

banq發表於2022-03-14

假設您正在使用 Java 進行某種資料分析,也許您正在分析交易(如銷售)。Transaction在對物件執行計算之前,您需要評估複雜的過濾器。

  public static final class Transaction {
    private final int quantity;
    private final long price;
    private final long timestamp;

    public Transaction(int quantity, long price, long timestamp) {
      this.quantity = quantity;
      this.price = price;
      this.timestamp = timestamp;
    }

    public int getQuantity() {
      return quantity;
    }

    public long getPrice() {
      return price;
    }

    public long getTimestamp() {
      return timestamp;
    }
  }


在 Java 程式中進行過濾使用流 API:

  transactions.stream()
    .filter(transaction -> transaction.quantity >= qty && transaction.price <= price
                    && transaction.timestamp >= begin && transaction.timestamp <= end)
            .forEach(this::processTransaction);



假設交易實際上是按時間排序的,那麼時間戳條件應該是可預測的,但並沒有首先評估。
這意味著不可預測的價格price 和數量quantity 條件要對每個交易進行評估。
如果重新安排條件的順序可以使執行時間縮短一半。
  

RangeBitmap
將範圍謂詞應用於未排序的資料,由RoaringBitmap庫RangeBitmap中的資料結構解決。
資料結構是不可變的,並且受益於對資料集中值範圍的瞭解,但是如果需要評估多個過濾器,則為每個屬性建立索引可能是值得的。

   long minTimestamp = Long.MAX_VALUE;
    long maxTimestamp = Long.MIN_VALUE;
    long minPrice = Long.MAX_VALUE;
    long maxPrice = Long.MIN_VALUE;
    int minQty = Long.MAX_VALUE;
    int maxQty = Long.MIN_VALUE;
    for (Transaction transaction : transactions) {
        minTimestamp = Math.min(minTimestamp, transaction.getTimestamp());
        maxTimestamp = Math.max(maxTimestamp, transaction.getTimestamp());
        minPrice = Math.min(minPrice, transaction.getPrice());
        maxPrice = Math.max(maxPrice, transaction.getPrice());
        minQty = Math.min(minQty, transaction.getQuantity());
        maxQty = Math.max(maxQty, transaction.getQuantity());
    }
    var timestampAppender = RangeBitmap.appender(maxTimestamp - minTimestamp);
    var priceAppender = RangeBitmap.appender(maxPrice - minPrice);
    var qtyAppender = RangeBitmap.appender(maxQty - minQty);
    for (Transaction transaction : transactions) {
        timestampAppender.add(transaction.getTimestamp() - minTimestamp);
        priceAppender.add(transaction.getPrice() - minPrice);
        qtyAppender.add(transaction.getQuantity() - minQty);
    }
    var timestampIndex = timestampAppender.build();
    var priceIndex = priceAppender.build();
    var qtyIndex = qtyAppender.build();


兩者是否傳遞資料或半頁程式碼是否值得取決於您需要執行多少過濾器以及它們需要多快。
RangeBitmap產生一個RoaringBitmap滿足謂詞的索引,並且可以將RoaringBitmap引數作為輸入來跳過已經過濾掉的行。之前使用的 Streams API 程式碼被翻譯成RangeBitmapAPI 呼叫:

  RoaringBitmap inTimeRange = timestampIndex.between(minTimeThreshold - minTime, maxTimeThreshold - minTime);
    RoaringBitmap matchesQuantity = qtyIndex.gte(minQtyThreshold - minQty, inTimeRange);
    RoaringBitmap matchesPrice = priceIndex.lte(maxPriceThreshold - minPrice, matchesQuantity);
    matchesPrice.forEach((IntConsumer) i -> processTransaction(transactions.get(i)));


每個屬性的最小值錨定有點複雜,但提高了效率(除非最小值無論如何都是零),這將在實際應用程式中透過便利類更好地抽象。對於相同的資料,這比二分搜尋方法快約 2 倍.

RangeBitmap旨在為Apache Pinot中的範圍索引提供支援,因此被壓縮並支援與磁碟的零複製對映。RangeBitmap - How range index work in Apache Pinot 中有更多關於它如何深入工作的詳細資訊。

相關文章