【问题标题】:Higher memory allocation rates for List of single-character String than multi-character String单字符串列表的内存分配率高于多字符串列表
【发布时间】:2020-07-05 11:01:37
【问题描述】:

考虑以下基准,它分配长度为 1 的 StringList 与长度为 8 的对比

@State(Scope.Benchmark)
@BenchmarkMode(Array(Mode.Throughput))
class SoMemory {
  val size = 1_000_000
  @Benchmark def a: List[String] = List.fill[String](size)(Random.nextString(1))
  @Benchmark def b: List[String] = List.fill[String](size)(Random.nextString(8))
}

sbt "jmh:run -i 10 -wi 10 -f 2 -t 1 -prof gc bench.SoMemory" 给出的地方

[info] Benchmark                                     Mode  Cnt           Score          Error   Units
[info] SoMemory.a                                   thrpt   20          16.650 ±        0.519   ops/s
[info] SoMemory.a:·gc.alloc.rate                    thrpt   20        3870.364 ±      120.687  MB/sec
[info] SoMemory.a:·gc.alloc.rate.norm               thrpt   20   255963282.822 ±       61.012    B/op
[info] SoMemory.a:·gc.churn.PS_Eden_Space           thrpt   20        3862.090 ±      161.598  MB/sec
[info] SoMemory.a:·gc.churn.PS_Eden_Space.norm      thrpt   20   255331784.446 ±  4839869.981    B/op
[info] SoMemory.a:·gc.churn.PS_Survivor_Space       thrpt   20          25.893 ±        1.433  MB/sec
[info] SoMemory.a:·gc.churn.PS_Survivor_Space.norm  thrpt   20     1711320.051 ±    64870.177    B/op
[info] SoMemory.a:·gc.count                         thrpt   20         318.000                 counts
[info] SoMemory.a:·gc.time                          thrpt   20       45183.000                     ms
[info] SoMemory.b                                   thrpt   20           2.859 ±        0.092   ops/s
[info] SoMemory.b:·gc.alloc.rate                    thrpt   20        2763.961 ±       89.654  MB/sec
[info] SoMemory.b:·gc.alloc.rate.norm               thrpt   20  1063705990.899 ±      503.169    B/op
[info] SoMemory.b:·gc.churn.PS_Eden_Space           thrpt   20        2768.433 ±      101.742  MB/sec
[info] SoMemory.b:·gc.churn.PS_Eden_Space.norm      thrpt   20  1065601049.380 ± 25878705.006    B/op
[info] SoMemory.b:·gc.churn.PS_Survivor_Space       thrpt   20          20.838 ±        1.063  MB/sec
[info] SoMemory.b:·gc.churn.PS_Survivor_Space.norm  thrpt   20     8015328.037 ±   236873.550    B/op
[info] SoMemory.b:·gc.count                         thrpt   20         234.000                 counts
[info] SoMemory.b:·gc.time                          thrpt   20       37696.000                     ms

注意较小的字符串如何显着提高gc.alloc.rate

SoMemory.a:·gc.alloc.rate         thrpt   20        3870.364 ±      120.687  MB/sec
SoMemory.b:·gc.alloc.rate         thrpt   20        2763.961 ±       89.654  MB/sec

为什么在第一种情况下,较小的字符串应该具有较小的内存占用,例如,JOL 给出的内存消耗似乎更高

class ZarA { val x = List.fill[String](1_000_000)(Random.nextString(1)) }
class ZarB { val x = List.fill[String](1_000_000)(Random.nextString(8)) }

ZarA 的占用空间更小,约为 72MB

example.ZarA@15975490d footprint:
     COUNT       AVG       SUM   DESCRIPTION
   1000000        24  24000000   [C
         1        16        16   example.ZarA
   1000000        24  24000000   java.lang.String
   1000000        24  24000000   scala.collection.immutable.$colon$colon
         1        16        16   scala.collection.immutable.Nil$
   3000002            72000032   (total)

ZarB 大约 80MB 的更大占用空间相比

example.ZarB@15975490d footprint:
     COUNT       AVG       SUM   DESCRIPTION
   1000000        32  32000000   [C
         1        16        16   example.ZarB
   1000000        24  24000000   java.lang.String
   1000000        24  24000000   scala.collection.immutable.$colon$colon
         1        16        16   scala.collection.immutable.Nil$
   3000002            80000032   (total)

VisualVM 内存行为

ZarA - 已用堆 129 MB

ZarB - 已用堆 91 MB

【问题讨论】:

    标签: string list scala scala-collections memory-consumption


    【解决方案1】:

    分配率是您分配内存的速度(每单位时间分配的内存量)。它没有告诉我们任何有关分配的总内存的信息。

    找到更大的连续内存区域总是更容易找到更小的连续内存区域,例如分配例如1000 个长度为 1 的字符串应该花费比分配更少的时间,例如1000 长度为 8 的字符串,分配率更高,总内存消耗更少。

    【讨论】:

    • 使用alvinalexander.com/scala/… 似乎表明ZarA 会消耗更多的总内存。
    • 更高的gc.alloc.rate.norm 是否表示更高的总内存消耗?
    • Norm 代表标准化,但它仍然是速率(速度),而不是整体分配。这些 JMH 指标都不能说明总内存使用情况,我认为 JMH 不是衡量内存消耗的正确工具。为此,一些分析器会好得多。从Runtime 打印内存会告诉您某个时间点的内存使用情况,我们不知道它是在 GC 之前还是之后(分析器可以强制 GC 为我们提供更清晰的图片),所以恕我直言,这可能是有道理的仅作为一些内存使用直方图的输入。
    • 尝试强制 GC 并查看已分配对象的列表,这应该会告诉您一些有趣的事情。
    • 在使用System.gc() 强制 GC 后,ZarA 的堆使用量约为 75 MB,而 ZarB 的堆使用量约为 85 MB,这确实类似于 JOL 预测。谢谢,这澄清了它。
    猜你喜欢
    • 2023-03-28
    • 2017-07-24
    • 1970-01-01
    • 1970-01-01
    • 2021-01-28
    • 2022-10-19
    • 1970-01-01
    • 1970-01-01
    • 2018-03-18
    相关资源
    最近更新 更多