Использование штучных / атомарных значений в Scala с Chronicle Map

Мы используем ChronicleMap для поддержки сохранности вне кучи в большом количестве разных магазинов, но немного пострадали от проблемы с самым простым вариантом использования.

Прежде всего, вот помощник, который я написал, чтобы облегчить создание:

import java.io.File
import java.util.concurrent.atomic.AtomicLong

import com.madhukaraphatak.sizeof.SizeEstimator
import net.openhft.chronicle.map.{ChronicleMap, ChronicleMapBuilder}

import scala.reflect.ClassTag

object ChronicleHelper {

  def estimateSizes[Key, Value](data: Iterator[(Key, Value)], keyEstimator: AnyRef => Long = defaultEstimator, valueEstimator: AnyRef => Long = defaultEstimator): (Long, Long, Long) = {
    println("Estimating sizes...")

    val entries = new AtomicLong(1)
    val keySum = new AtomicLong(1)
    val valueSum = new AtomicLong(1)
    var i = 0

    val GroupSize = 5000

    data.grouped(GroupSize).foreach { chunk =>

      chunk.par.foreach { case (key, value) =>
        entries.incrementAndGet()
        keySum.addAndGet(keyEstimator(key.asInstanceOf[AnyRef]))
        valueSum.addAndGet(valueEstimator(value.asInstanceOf[AnyRef]))
      }

      i += 1

      println("Progress:" + i * GroupSize)
    }

    (entries.get(), keySum.get() / entries.get(), valueSum.get() / entries.get())
  }

  def defaultEstimator(v: AnyRef): Long = SizeEstimator.estimate(v)

  def createMap[Key: ClassTag, Value: ClassTag](data: => Iterator[(Key, Value)], file: File): ChronicleMap[Key, Value] = {
    val keyClass = implicitly[ClassTag[Key]].runtimeClass.asInstanceOf[Class[Key]]
    val valueClass = implicitly[ClassTag[Value]].runtimeClass.asInstanceOf[Class[Value]]

    val (entries, averageKeySize, averageValueSize) = estimateSizes(data)

    val builder = ChronicleMapBuilder.of(keyClass, valueClass)
      .entries(entries)
      .averageKeySize(averageKeySize)
      .averageValueSize(averageValueSize)
      .asInstanceOf[ChronicleMapBuilder[Key, Value]]

    val cmap = builder.createPersistedTo(file)

    val GroupSize = 5000

    println("Inserting data...")
    var i = 0
    data.grouped(GroupSize).foreach { chunk =>

      chunk.par.foreach { case (key, value) =>
        cmap.put(key, value)
      }

      i += 1

      println("Progress:" + i * GroupSize)
    }

    cmap
  }

  def empty[Key: ClassTag, Value: ClassTag]: ChronicleMap[Key, Value] = {
    val keyClass = implicitly[ClassTag[Key]].runtimeClass.asInstanceOf[Class[Key]]
    val valueClass = implicitly[ClassTag[Value]].runtimeClass.asInstanceOf[Class[Value]]


    ChronicleMapBuilder.of(keyClass, valueClass).create()
  }


  def loadMap[Key: ClassTag, Value: ClassTag](file: File): ChronicleMap[Key, Value] = {
    val keyClass = implicitly[ClassTag[Key]].runtimeClass.asInstanceOf[Class[Key]]
    val valueClass = implicitly[ClassTag[Value]].runtimeClass.asInstanceOf[Class[Value]]

    ChronicleMapBuilder.of(keyClass, valueClass).createPersistedTo(file)
  }
}

Он использует https://github.com/phatak-dev/java-sizeof для оценки размера объекта. Вот тип использования, который мы хотим поддерживать:

object TestChronicle {
  def main(args: Array[String]) {
    def dataIterator: Iterator[(String, Int)] = (1 to 5000).toIterator.zipWithIndex.map(x => x.copy(_1 = x._1.toString))

    ChronicleHelper.createMap[String, Int](dataIterator, new File("/tmp/test.map"))

  }
}

Но это исключение:

[ошибка] Исключение в потоке "main" java.lang.ClassCastException: ключ должен быть целым, но был классом java.lang.Integer [ошибка] в net.openhft.chronicle.hash.impl.VanillaChronicleHash.checkKey(VanillaChronicleHash.java:661) [ошибка] в net.openhft.chronicle.map.VanillaChronicleMap.queryContext(VanillaChronicleMap.java:281) [ошибка] в net.openhft.chronicle.map.VanillaChronicleMap.put(VanillaChronicleMap.java:390) в...

Я вижу, что это может быть связано с атомарностью Int Scala, а не с Integer Java, но как мне это обойти?

Scala 2.11.7

Хроника Карта 3.8.0

1 ответ

  • Кажется подозрительным, что в вашем тесте это Iterator[(String, Int)] (скорее, чем Iterator[(Int, String)]) для типа ключа есть String и тип значения Int, пока идет сообщение об ошибке о типе ключа (int/Integer)
  • Если сообщение об ошибке говорит Key must be a %type% это означает, что вы настроили этот тип в первом ChronicleMapBuilder.of(keyType, valueType) заявление. Так что в вашем случае это означает, что вы настроили int.class (Class объект, представляющий примитив int введите в Java), что не допускается, и предоставление java.lang.Integer экземпляр методов карты (возможно, вы предоставляете примитив intс, но они становятся Integer из-за бокса), это разрешено. Вы должны убедиться, что вы предоставляете java.lang.Integer.class (или какой-то другой класс Скала), чтобы ChronicleMapBuilder.of(keyType, valueType) вызов.
  • Я не знаю, какую оценку размера дает этот проект: https://github.com/phatak-dev/java-sizeof, но в любом случае вы должны указать размер в байтах, который будет принимать объект в сериализованной форме. Сама сериализованная форма зависит от сериализаторов по умолчанию, выбранных для определенного типа в Chronicle Map (и может меняться между версиями Chronicle Map), или от пользовательских сериализаторов, настроенных для конкретных ChronicleMapBuilder, Поэтому использование любой информации о "размерах" ключа / значения для настройки Chronicle Map, за исключением самой Chronicle Map, является хрупким. Вы можете использовать следующую процедуру для более надежной оценки размеров:

    public static <V> double averageValueSize(Class<V> valueClass, Iterable<V> values) {
        try (ChronicleMap<Integer, V> testMap = ChronicleMap.of(Integer.class, valueClass)
            // doesn't matter, anyway not a single value will be written to a map
                .averageValueSize(1)
                .entries(1)
                .create()) {
            LongSummaryStatistics statistics = new LongSummaryStatistics();
            for (V value : values) {
                try (MapSegmentContext<Integer, V, ?> c = testMap.segmentContext(0)) {
                    statistics.accept(c.wrapValueAsData(value).size());
                }
            }
            return statistics.getAverage();
        }
    }
    

    Вы можете найти его в этом тесте: https://github.com/OpenHFT/Chronicle-Map/blob/7aedfba7a814578a023f7975ef15ba88b4d435db/src/test/java/eg/AverageValueSizeTest.java

    Эта процедура хакерская, но лучших вариантов сейчас нет.

Еще одна рекомендация:

  • Если ваши ключи или значения являются разновидностями примитивов (целые, длинные, двойные, но в штучной упаковке), или любого другого типа, который всегда имеет одинаковый размер, вы не должны использовать averageKey/averageValue/averageKeySize/averageValueSize методы, лучше вы используете constantKeySizeBySample/constantValueSizeBySample метод. Специально для java.lang.Integer, Long а также Double даже в этом нет необходимости, Chronicle Map уже знает, что эти типы постоянно измеряются.
Другие вопросы по тегам