Search code examples
clojureprofilingvisualvm

Interpreting a VisualVM backtrace


I'm doing CPU profiling on my Mandelbrot Set explorer. For some reason, java.lang.PersistentHashMap$BitmapIndexedNode.find is using a fairly large percentage of the total CPU time. When I take a snapshot of the profiling results, and get a backtrace of the method, I get this:

VisualVM Backtrace Image

I see lots of references to BigDecimal operations. It seems as though BigDecimal operators at some point require calling find on a PersistentHashMap.

Is my interpretation of the backtrace correct? Are calls to find a result of BigDecimal operations, meaning there's nothing I can do about it? That seems like an odd thing for them to require. I'm having a hard time digging deeper than clojure.lang.Numbers$BigDecimalOps though to verify this though.


Solution

  • Your interpretation is correct. Addition, multiplication, negation, division and other BigDecimal operations end up doing hash maps look ups. Those are a part of de-referencing the *math-context* Var. It happens every time an arithmetic operation on two BigDecimal objects is performed in Clojure. There is nothing that can be done about it short of switching to other numerical types, like double.

    The clojure.core/*math-context* dynamic Var does not have a docstring. As far as I can tell, it is intended to hold a java.math.MathContext object. MathContext objects can be used to specify precision and rounding mode for BigDecimal operations. If *math-context* is bound, its value is passed to BigDecimal methods in the Java runtime as in BigDecimal.add(BigDecimal augend, MathContext mc). When *math-context* is not bound, BigDecimal methods are called without passing a context.

    The relevant part of the stack trace from the question is:

    ...
    clojure.lang.PersistentHashMap.entryAt(Object)
    clojure.lang.Var.getThreadBinding()
    clojure.lang.Var.deref()
    clojure.lang.Numbers$BigDecimalOps.add/multiply/..
    ...
    

    Some pointers to Clojure source code:

    • *math-context* Var defined
    • *math-context* de-referenced when BigDecimal addition is performed. Same thing happens for other operations too
    • Dereferencing a Var requires a call to getThreadBinding
    • A hash map is looked up inside getThreadBinding