2

我们正在使用 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"))

  }
}

但它抛出了一个异常:

[错误] 线程“主”java.lang.ClassCastException 中的异常:键必须是 int,但在 net.openhft.chronicle.hash.impl.VanillaChronicleHash.checkKey(VanillaChronicleHash.java) 是类 java.lang.Integer [错误] :661) [错误] 在 net.openhft.chronicle.map.VanillaChronicleMap.queryContext(VanillaChronicleMap.java:281) [错误] 在 net.openhft.chronicle.map.VanillaChronicleMap.put(VanillaChronicleMap.java:390) [错误]在 ...

我可以看到它可能与 Scala 的 Int 的原子性有关,而不是 Java 的 Integer,但我该如何绕过呢?

斯卡拉 2.11.7

编年史地图 3.8.0

4

1 回答 1

1
  • 似乎怀疑在您的测试中它Iterator[(String, Int)](而不是Iterator[(Int, String)])键类型是String和值类型是Int,而错误消息是关于键的类型(int / Integer)
  • 如果错误消息说Key must be a %type%这意味着您在第一ChronicleMapBuilder.of(keyType, valueType)条语句中配置了该类型。因此,在您的情况下,这意味着您已配置int.classClass对象,表示 Java 中的原始int类型),这是不允许的,并为java.lang.Integermap 的方法提供实例(可能您提供了原语int,但它们Integer由于装箱而成为),这是允许的. 您应该确保您正在提供java.lang.Integer.class(或其他一些 Scala 的类)来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.IntegerLong甚至Double不需要,Chronicle Map 已经知道这些类型是不断调整大小的。
于 2016-04-12T01:18:03.380 回答