2016-12-16 8 views
1

У меня есть функция memory, которая принимает функцию и измеряет использование памяти его:Меры блока кода

import java.lang.management.ManagementFactory 

def memory[T](
    f: ⇒ T 
)(
    mu: Long ⇒ Unit 
): T = { 
    val memoryMXBean = ManagementFactory.getMemoryMXBean 
    memoryMXBean.gc() 
    val usedBefore = memoryMXBean.getHeapMemoryUsage.getUsed 
    println(s"${memoryMXBean.getObjectPendingFinalizationCount()} pending, used $usedBefore") 
    val r = f 
    memoryMXBean.gc() 
    val usedAfter = memoryMXBean.getHeapMemoryUsage.getUsed 
    println(s"${memoryMXBean.getObjectPendingFinalizationCount()} pending, used $usedAfter") 
    mu(usedAfter - usedBefore) 
    r 
} 

Получение объема памяти, используемой new Array[Byte](1024*1024) должен возвращать 1 МБ.

memory{new Array[Byte](1024*1024)}{r=>println(s"$r byte")} 

Но самый первый вызов памяти возвращает отрицательный результат, последующие вызовы мера (даже с разными комбинезоны) использование памяти просто отлично:

scala> memory{new Array[Byte](1024*1024)}{r=>println(s"$r byte")} 
0 pending, used 45145040 
0 pending, used 45210384 
65344 byte    <- 65kb != 1MB 

scala> memory{new Array[Byte](1024*1024)}{r=>println(s"$r byte")} 
0 pending, used 45304512 
0 pending, used 46353104 
1048592 byte    <- Correct 

Где-то между этими двумя memoryMXBean.getHeapMemoryUsage что-то будет освобожден, но там, где не ожидается освобождение ожидающего объекта. Такое поведение может быть также определено, когда у вас есть пустое тело (не забудьте перезапустить консоль SCALA, чтобы получить этот результат):

scala> memory{}{r=>println(s"$r byte")} 
0 pending, used 44917584 
0 pending, used 44025552 
-892032 byte    <- 800kb less memory? 

scala> memory{}{r=>println(s"$r byte")} 
0 pending, used 44070440 
0 pending, used 44069960 
-480 byte     <- This is ok 

Кроме того, выполняющему gc() и getHeapMemoryUsage на консоли производит этот результат:

scala> import java.lang.management.ManagementFactory; val memoryMXBean = ManagementFactory.getMemoryMXBean; memoryMXBean.setVerbose(true) 
import java.lang.management.ManagementFactory 
memoryMXBean: java.lang.management.MemoryMXBean = [email protected] 

scala> memoryMXBean.gc(); memoryMXBean.getHeapMemoryUsage 
[GC (System.gc()) 57400K->44462K(109056K), 0,0148555 secs] 
[Full GC (System.gc()) 44462K->39602K(109056K), 0,2641397 secs] 
res1: java.lang.management.MemoryUsage = init = 33554432(32768K) used = 41358440(40389K) committed = 111673344(109056K) max = 239075328(233472K) 

scala> memoryMXBean.gc(); memoryMXBean.getHeapMemoryUsage 
[GC (System.gc()) 46702K->40258K(111104K), 0,0025801 secs] 
[Full GC (System.gc()) 40258K->39631K(111104K), 0,1988796 secs] 
res2: java.lang.management.MemoryUsage = init = 33554432(32768K) used = 40583120(39631K) committed = 113770496(111104K) max = 239075328(233472K) 

41358440 - 40583120 = 775320, почти 800kb нет использование памяти (смотрите used).

Почему первое измерение возвращает неправильный результат? Есть ли способ исправить это, кроме запуска метода дважды?

Использование Scala 2.12.1-20161205-201300-2787b47 (OpenJDK 64-Bit Server VM, Java 1.8.0_112) в Arch Linux.

Спасибо!

ответ

3

Использование Jamm

Если вы хотите, чтобы проверить, сколько памяти структура данных на JVM потребляет, вы должны смотреть в инструментальные библиотеки, такие как JAMM. Он работает путем перемещения объекта-объекта объекта, который вы хотите измерить, и использования знаний о макете памяти на JVM, на котором вы работаете.

Обратите внимание, что данные, которые вы получите обратно специфичен для JVM версии и архитектуры вы используете. В разных архитектурах потребление памяти может отличаться из-за разных размеров и кодировки указателя. И на разных JVM, даже макет памяти может отличаться.

Тем не менее, это мощный инструмент для реализации высокоэффективных структур данных на JVM.

Вот как вы будете использовать Jamm из Скале:

val o = new Array[Byte](1024*1024) 
val mm = new MemoryMeter() 
println("Size of new Array[Byte](1024*1024): " + mm.measureDeep(o)) 

И вот результат:

Size of new Array[Byte](1024*1024): 1048592 

Библиотека Jamm является Java-агент, который перехватывает в JVM. Поэтому использование JAMM требует загрузки jamm-баночки и добавления параметра (например, -javaagent:jamm-0.3.0.jar) в параметры java, предпочтительно используя ключ javaOptions sbt.

Автоматизированная память тесты

Обратите внимание, что если вы полагаться на компакт-представление в памяти для некоторых структур данных, которые вы пишете, вы должны иметь автоматизированные тесты, обеспечивающие представление в памяти, как вы ожидаете , Для вдохновения о том, как это установить, вот minimal project, который импортирует и настраивает java-агент JAMM для тестов.

Чтобы поиграть, вы можете просто добавить свой тестовый код к JammTest и запустить его с помощью sbt test:run.

+0

Спасибо за ваше предложение. Это полезно для измерения размера возвращаемого значения, но вы не можете измерить общее распределение памяти побочных эффектов, не так ли? Например, у вас есть 'var L: List.empty [String]', и вы хотите измерить 'measure {fillLwithData()} {...}'. – amuttsch

+0

Тогда я не понимаю, чего вы хотите. Измерьте потребление данных в структуре данных: используйте JAMM. Измерить общие ассигнования, включая временные: используйте что-то вроде https://github.com/google/allocation-instrumenter. Все, что происходит на JVM, можно точно измерить. –

1

Проблема заключается в том, что использование памяти не точно учитывается для повышения производительности. Это показано в двух областях:

  • Используемая память предназначена для живых объектов и объектов, которые еще не собраны. Когда вы создаете большой объект, вы можете запускать коллекцию и в конечном итоге использовать меньше памяти, чем раньше.
  • Меньшие объекты выделяются из локального буфера распределения потоков или TLAB. TLAB является локальным буфером для каждого потока, чтобы минимизировать конфликт на пространстве Eden и, таким образом, позволять потоку распределять одновременно. С другой стороны, вы не видите, сколько из каждого из этих TLAB используется, и иногда видят большие прыжки в использовании. Простой способ обойти это, чтобы превратить TLABs от -XX:-UseTLAB и вы получите точный учет даже для new Object() (Предполагая GC не происходит)
Смежные вопросы