Debug counters are a general infrastructure for counting arbitrary things.
There are two types of debug counters: static and dynamic. Static counters do not add anything to the code generated by the compiler, but it counts events at compile-time while dynamic counters are incremented at runtime and are used to count events in the code generated by the compiler.
Debug counters can be inserted either at the tree level or at the instruction level. Note that counters are not actually inserted unless the counter name and type match the provided compiler options (see Specifying Output below).
The different overloads of TR::Compilation::prependDebugCounter
allow you to inject counter bumps into trees.
TR::DebugCounter::prependDebugCounter(comp(), "inliner.callSites/failed/coldCallee/1", treetop);
The different overloads of TR::CodeGenerator::generateDebugCounter
allow you to inject counter bumps into instructions.
cg->generateDebugCounter("RegisterAllocator/Exchange/FPR", 1, TR::DebugCounter::Free);
One (optional) argument when calling TR::Compilation::prependDebugCounter
or TR::CodeGenerator::generateDebugCounter
is fidelity. Consumers
typically use values from the TR::DebugCounter::Fidelities
enum. This
allows the caller to estimate how expensive the debug counter will be
to compute at runtime (e.g. Exorbitant
for counters that frequently
occur in hot paths, or Moderate
for counters in cold paths).
A certain fidelity can be excluded by using -Xjit:debugCounterFidelity=##
option,
which disables dynamic debug counters with the fidelity rating below the specified number.
Whenever you use a counter via either TR::Compilation::prependDebugCounter
or TR::CodeGenerator::generateDebugCounter
, two counters are actually
created: one static, one dynamic.
- Dynamic counters are implemented by injecting nodes into trees or instructions into the generated code, and they count how often the particular code path is reached at runtime.
- Static counters do not add anything to the code generated by the JIT,
but serve to count events at compilation when the
prependDebugCounter
orgenerateDebugCounter
method is called.
The delta used for each counter can be specified separately using the available overloads, though they are the same by default.
incStaticDebugCounter
creates a static debug counter without a dynamic counterpart.
Static debug counters are used quite a bit in the optimizer to count compile
events that we do not need or cannot count at runtime. That is to say static-only
counters can and do exist, but dynamic counters get static counterparts by default
since knowing the ratio of the number of counters inserted vs the number
of counter bumps observed at runtime is generally useful for a dynamic counter.
Counter names modify the display processing logic for counters, which affects how the results are shown to the user. Special characters in a counter name signal the debug counter facility to treat a counter in a special way.
/
The most important special character is the slash. A slash in a counter name is meant to resemble a slash in a filename: it represents a hierarchical containment relation between counters. Asking for a counter with a name like "a/b" actually provides two counters: "a" and "a/b". The debug counter runtime will ensure that each increment of "a/b" also increments "a".
For example, suppose you added the following counters to the inliner:
prependDebugCounter("callSites/inlined", tt);
...
prependDebugCounter("callSites/notInlined", tt);
This will actually produce three counters with the following names:
callSites
callSites/inlined
callSites/notInlined
Increment trees will be inserted for counters 2 and 3, and the runtime will make sure their values are rolled into counter 1. Counter 1 is referred to as a "denominator counter" of counters 2 and 3. You may end up with a report that looks like this:
1: callSites | 10000 | __ 1 __
2: callSites/inlined | 8000 | 80.00% | __ 2 __
3: callSites/notInlined | 2000 | 20.00% | __ 3 __
The first column is the raw counter values. To the right are additional columns that give ratios between counters. In this example, the second column has a header of "__ 1__" meaning that the values below are relative to counter 1. The rightmost column just lists the counter numbers again, making it easier visually to follow the (sometimes very long) lines horizontally. Note that the compiled code would only contain counters 2 & 3, and the denominator (1) is computed from them.
:
A colon in a counter name is similar to a slash, except it indicates that the denominator should not be computed automatically. Rather, the developer will insert the denominator counter separately. The colon simply indicates that the user wants to compute the ratio between two counters. Asking for a counter with a name of the form "a:b" actually provides two counters: "a" and "a:b". The ratio between these two counters will be displayed in the counter report, but increments to "a:b" will not be counted as increments to "a".
For example:
prependDebugCounter("frames", tt);
...
prependDebugCounter("frames:#autos", tt, numAutos);
The JIT option to be used here is
-Xjit:'debugCounters={frames*}'
This produces two counters that are unrelated except that the compiler will report their ratio when the run is complete:
1: frames | 3914036134 | __ 1 __
2: frames:#autos | 22680880758 | 5.79 | __ 2 __
Looking down from the "__ 1__" heading, you can see that each frame has an average of 5.79 autos.
=
The equals sign indicates to the debug counter display routine that the remainder of the counter name should be sorted numerically rather than alphabetically. It has no other special meaning.
()
Parentheses are used to mark a verbatim section of a counter name, within which the other special characters lose their special meaning. For example, asking for a counter with a name of the form "(a/b)" produces just one counter. Without the special meaning of parentheses, this would have produced two counters: "(a" and "(a/b)".
Parentheses can be nested.
#
The hash symbol has no particular special meaning, but is used by convention to signify counters that are suitable for producing a histogram. Generally you can include a hash symbol in any debug counter that is incremented by some value other than 1, such as "#parameters".
If you require formatted debug counter names (subsequences) beginning
with %
, use TR::DebugCounter::debugCounterName(TR::Compilation *comp, const char *format, ...)
.
For example:
TR::DebugCounter::prependDebugCounter(comp(), TR::DebugCounter::debugCounterName(comp(), "inlineClone.location/array/(%s)", comp()->signature()), callTree);
Counters can have arbitrary names, though care should be taken with certain special characters:
- Whitespace and other shell-control characters may make them awkward to specify on the command line
- Characters with special meanings in the compiler's regular expressions will make them awkward to filter
TR_DebugCounterFileName
Specifies the file to which the debug counter output should be appended (creating the file if necessary), relative to the current working directory. If not specified, debug counter output is written to stdout.
For example:
export TR_DebugCounterFileName=debugcounters.txt
A full listing of debug counter-related JIT options can be found by
exporting {debugCounter} to the TR_Options
environment variable.
For example: TR_Options='help={*debugCounter*}' testjit
At least one debug counter option must be specified for debug counter output to be produced.
debugCounters=
Specifies a regular expression that is matched against dynamic debug counter names to determine which are included in the output.
For example: -Xjit:debugCounters={arraycopy/16Bit*|arraycopy/32Bit*|arraycopy/64Bit*}
staticDebugCounters=
Specifies a regular expression that is matched against static debug counter names to determine which are included in the output.
For example: -Xjit:staticDebugCounters={vt-helper/*}