Pinpointing Performance Inefficiencies in Java

06/28/2019 ∙ by Pengfei Su, et al. ∙ William & Mary 0

Many performance inefficiencies such as inappropriate choice of algorithms or data structures, developers' inattention to performance, and missed compiler optimizations show up as wasteful memory operations. Wasteful memory operations are those that produce/consume data to/from memory that may have been avoided. We present, JXPerf, a lightweight performance analysis tool for pinpointing wasteful memory operations in Java programs. Traditional byte-code instrumentation for such analysis (1) introduces prohibitive overheads and (2) misses inefficiencies in machine code generation. JXPerf overcomes both of these problems. JXPerf uses hardware performance monitoring units to sample memory locations accessed by a program and uses hardware debug registers to monitor subsequent accesses to the same memory. The result is a lightweight measurement at machine-code level with attribution of inefficiencies to their provenance: machine and source code within full calling contexts. JXPerf introduces only 7 production. Guided by JXPerf, we optimize several Java applications by improving code generation and choosing superior data structures and algorithms, which yield significant speedups.

READ FULL TEXT VIEW PDF
POST COMMENT

Comments

There are no comments yet.

Authors

page 1

page 2

page 3

page 4

This week in AI

Get the week's most popular data science and artificial intelligence research sent straight to your inbox every Saturday.

1. Introduction

Managed languages, such as Java, have become increasingly popular in various domains, including web services, graphic interfaces, and mobile computing. Although managed languages significantly improve development velocity, they often suffer from worse performance compared with native languages. Being a step removed from the underlying hardware is one of the performance handicaps of programming in managed languages. Despite their best efforts, programmers, compilers, runtimes, and layers of libraries, can easily introduce various subtleties to find performance inefficiencies in managed program executions. Such inefficiencies can easily go unnoticed (if not carefully and periodically monitored) or remain hard to diagnose (due to layers of abstraction and detachment from the underlying code generation, libraries, and runtimes).

Performance profiles abound in the Java world to aid developers to understand their program behavior. Profiling for execution hotspots is the most popular one (perf; Levon:OProfile; jprofiler-WWW; yourkit-WWW; visualvm-WWW; oracle-studio-WWW). Hotspot analysis tools identify code regions that are frequently executed disregarding whether execution is efficient or inefficient (useful or wasteful) and hence significant burden is on the developer to make a judgement call on whether there is scope to optimize a hotspot. Derived metrics such as Cycles-Per-Instruction (CPI) or cache miss ratio offer slightly better intuition into hotspots but are still not a panacea. Consider a loop repeatedly computing the exponential of the same number, which is obviously a wasteful work; the CPI metric simply acclaims such code with a low CPI value, which is considered a metric of goodness.

There is a need for tools that specifically pinpoint wasteful work and guide developers to focus on code regions where the optimizations are demanded. Our observation, which is justified by myriad case studies in this paper, is that many inefficiencies show up as wasteful operations when inspected at the machine code level, and those which involve the memory subsystem are particularly egregious. Although this is not a new observation (Chabbi:2012:DTP:2259016.2259033; Wen:2017:REV:3037697.3037729; witch; Su:2019:RLS:3339505.3339628) in native languages, its application to Java code is new and the problem is particularly severe in managed languages. The following inefficiencies often show up as wasteful memory operations.

[leftmargin=*]

Algorithmic inefficiencies::

frequently performing a linear search shows up as frequently loading the same value from the same memory location.

Data structural inefficiencies::

using a dense array to store sparse data where the array is repeatedly reinitialized to store different data items shows up as frequent store-followed-by-store operations to the same memory location without an intervening load operation.

Suboptimal code generations::

missed inlining can show up as storing the same values to the same stack locations; missed scalar replacement shows up as loading the same value from the same, unmodified, memory location.

Developers’ inattention to performance::

recomputing the same method in successive loop iterations can show up as silent stores (consecutive writes of the same value to the same memory). For example, the Java implementation of NPB-3.0 benchmark IS (Bailey:1991:NPB:125826.125925) performs the expensive power method inside a loop and in each iteration, the power method pushes the same parameters on the same stack location. Interestingly, this inefficiency is absent in the C version of the code due to a careful implementation where the developer hoisted the power function out of the loop.

This list suffices to provide an intuition about the class of inefficiencies detectable by observing certain patterns of memory operations at runtime. Some recent Java profilers (Xu:2013:RTO:2509136.2509512; Nguyen:2013:CDC:2491411.2491416; Dhok:2016:DTG:2950290.2950360; toddler; ldoctor) identify inefficiencies of this form. However, these tools are based on exhaustive Java byte code instrumentation, which suffer from two drawbacks: (1) high (up to 200) runtime overhead, which prevents them from being used for production software; (2) missing insights into lower-level layers e.g., inefficiencies in machine code.

142for (int bit = 0,dual = 1; bit < logn; bit++,dual *= 2) {
143  ...
144  for (int a = 1; a < dual; a++) {
145    ...
146    for (int b = 0; b < n; b += 2*dual) {
147      int i = 2*b ;
148      int j = 2*(b + dual);
149      double z1_real = data[j];
150      double z1_imag = data[j+1];
151      double wd_real = w_real*z1_real - w_imag*z1_imag;
152      double wd_imag = w_real*z1_imag + w_imag*z1_real;
153     data[j] = data[i] - wd_real;
154      data[j+1] = data[i+1] - wd_imag;
155     data[i] += wd_real;
156      data[i+1] += wd_imag;
157    }}}
1\label{lst:fft}
2\end{figure}
3
4\begin{figure}[t]
5\begin{center}
6\includegraphics[width=0.3\textwidth]{fft-asm.pdf}
7\end{center}
8\vspace{-0.15in}
9\caption{The assembly code (at\&t style) of lines 153 and 155 in Listing~\ref{lst:fft}.}
10\vspace{-1em}
11\label{fig:fft}
12\end{figure}
13
14\subsection{A Motivating Example}
15\label{subsec:motivation}
16
17\sloppy
18Listing~\ref{lst:fft} shows a hot loop in a JIT-compiled (JITted) method (compiled with Oracle HotSpot JIT compiler) in SPECjvm2008 scimark.fft~\cite{SPEC:JVM2008}, a standard implementation of Fast Fourier Transforms.
19The JITted assembly code of the source code at lines \ref{L1} and \ref{L2} is shown in Figure~\ref{fig:fft}.
20Notice the two loads from the memory location \texttt{data[i]} (\texttt{0x10(\%r9,\%r8,8)}) --- once into  \texttt{xmm2} at line  \ref{L1} and then into  \texttt{xmm0} at line  \ref{L2}.
21\texttt{data[i]} is unmodified between these two loads.
22Moreover, \texttt{i} and \texttt{j} differ by at least $2$ and never alias to the same memory location (see lines~\ref{L3} and~\ref{L4}).
23Unfortunately, the code generation fails to exploit this aspect and trashes \texttt{xmm2} at line  \ref{L1}, which results in reloading \texttt{data[i]}  at line  \ref{L2}.
24
25With the knowledge of never-alias, we performed scalar replacement---placed \texttt{data[i]} in a temporary that eliminated the redundant load, which yielded a 1.13$\times$ speedup for the entire program.
26Without access to the source code of the commercial Java runtime, we cannot definitively say whether the alias analysis missed the opportunity or the register allocator caused the suboptimal code generation, most likely the former.
27However, it suffices to highlight the fact that observing the patterns of wasteful memory operations at the machine code level at runtime, divulges inefficiencies left out at various phases of transformation and allows us to peek into what ultimately executes.
28A more detailed analysis of this benchmark with the optimization guided by \jxperf{} follows in Section~\ref{subsec:fft}.
29
30\subsection{Contribution Summary}
31
32We propose \jxperf{} to complement existing Java profilers;  \jxperf{}  samples hardware performance counters and employs debug registers available in commodity CPUs to identify program inefficiencies that exhibit as wasteful memory operations at runtime.
33Two key differentiating aspects of \jxperf{} when compared to a large class of  hotspot profilers are its ability to (1) filter out and show code regions that are definitely involved in some kind of inefficiency at runtime (hotspot profilers cannot differentiate whether or not a code region is involved in any inefficiency), and (2) pinpoint the \emph{two parties} involved in wasteful work---e.g., the first instance of a memory access and a subsequent, unnecessary access of the same memory---which offer actionable insights (hotspot profilers are limited to showing only a single party).
34Via \jxperf{}, we make the following contributions:
35
36\begin{itemize}[leftmargin=*]
37  \item  We show the design and implementation of a lightweight Java inefficiency profiler working on off-the-shelf Java virtual machine (JVM) with no byte code instrumentation to memory accesses.
38  %with no instrumentation whatsoever.
39  \item We demonstrate that \jxperf{} identifies inefficiency at machine code, which can be introduced by poor code generation, inappropriate data structures, or suboptimal algorithms.
40%  \item  We demonstrate the design of combining PMUs, debug registers and JVM tool interface (JVMTI) to form an efficient Java profiler on the JITted machine code.
41 % \item  We describe how \jxperf{} collects metrics and calling contexts, and attributes to source code to provide intuitive guidance on inefficiency optimization.
42  \item  We perform a thorough evaluation on \jxperf{} and show that \jxperf{}, with 7\% runtime overhead and 7\% memory overhead, is able to pinpoint inefficiencies in well-known Java benchmarks and real-world applications, which yield significant speedups after eliminating such inefficiencies.
43\end{itemize}
44
45
46\subsection{Paper Organization}
47This paper is organized as follows.
48Section~\ref{sec:related} describes the related work and distinguishes \jxperf{}.
49Section~\ref{sec:background} offers the background knowledge necessary to understand  \jxperf{}.
50Section~\ref{sec:methodology} highlights the methodology we use to identify wasteful memory operations in Java programs.
51Section~\ref{sec:design} depicts the design and implementation of \jxperf{}.
52Section~\ref{sec:experiment} and~\ref{sec:use} evaluate \jxperf{} and show several case studies, respectively.
53Section~\ref{sec:threats} discusses the threats to validity.
54Section~\ref{sec:conclusion} presents our conclusions and future work.
55
56
57\section{Related Work}
58\label{sec:related}
59\begin{comment}
60Static analysis tools~\cite{pmd-WWW,findbugs-WWW,Guyer:2006:FSA:1133981.1134024,Liu:2014:CDP:2568225.2568229,Olivo:2015:SDA:2737924.2737966,Nistor:2015:CAD:2818754.2818863} detect redundant or useless operations via analyzing Java source code or byte code.
61They may produce false positives and false negatives due to imprecision and limited analysis scope; furthermore, they do not distinguish high-priority performance issues from low-priority ones because they are unaware of hot paths.
62A thorough literature review of static tools is outside the scope; we focus on dynamic analysis tools.
63\end{comment}
64
65There are a number of commercial and research Java profilers, most of which fall into the two categories: hotspot profilers and inefficiency profilers.
66\paragraph{\textbf{\textit{Hotspot Profilers.}}}
67Profilers such as Perf~\cite{perf}, Async-profiler~\cite{async-profiler-WWW}, Jprofiler~\cite{jprofiler-WWW}, YourKit~\cite{yourkit-WWW}, VisualVM~\cite{visualvm-WWW}, Oracle Developer Studio Performance Analyzer~\cite{oracle-studio-WWW}, and IBM Health Center~\cite{healthCenter-WWW} pinpoint hotspots in Java programs.
68Most hotspot profilers incur low overhead because they use interrupt-based sampling techniques supported by Performance Monitoring Units (PMUs) or OS timers.
69Hotspot profilers are able to identify code sections that account for a large number of CPU cycles, cache misses, branch mispredictions, heap memory usage, or floating point operations.
70While hotspot profilers are indispensable, they do not tell whether a resource is being used in a fruitful manner. For instance, they cannot report repeated memory stores of the identical value or result-equivalent computations, which squander both memory bandwidth and processor functional units.
71
72
73\paragraph{\textbf{\textit{Inefficiency Profilers.}}}
74Toddler~\cite{toddler} detects repeated memory loads in nested loops. LDoctor~\cite{ldoctor} combines static analysis and dynamic sampling techniques to reduce Toddler’s overhead. However, LDoctor detects inefficiencies in only a small number of suspicious loops instead of in the whole program.
75Glider~\cite{Dhok:2016:DTG:2950290.2950360} generates tests that expose redundant operations in Java collection traversals.
76MemoizeIt~\cite{DellaToffola:2015:PPY:2814270.2814290} detects redundant computations by identifying methods that repeatedly perform identical computations and output identical results.
77Xu et al. employ various static and dynamic analysis techniques (e.g., points-to analysis, dynamic slicing) to detect memory bloat by identifying useless data copying~\cite{Xu:2009:GFP:1542476.1542523}, inefficiently-used containers~\cite{Xu:2010:DIC:1806596.1806616}, low-utility data structures~\cite{Xu:2010:FLD:1806596.1806617}, reusable data structures~\cite{Xu:2013:RTO:2509136.2509512} and cacheable data structures~\cite{Nguyen:2013:CDC:2491411.2491416}.
78
79Unlike hotspot profilers, these tools can pinpoint redundant operations that lead to resource wastage.
80\jxperf{} is also an inefficiency profiler.
81Unlike prior works, which use exhaustive byte code instrumentation, \jxperf{} exploits features available in hardware (performance counters and debug registers) that eliminate instrumentation and dramatically reduces tool overheads.
82\jxperf{} detects multiple kinds of wasteful memory access patterns.
83Furthermore, \jxperf{} can be easily extended with additional memory access patterns for detecting other kinds of runtime inefficiencies.
84Section~\ref{sec:experiment} details the comparison between \jxperf{} and the profilers based on exhaustive byte code instrumentation.
85%Thus, \jxperf{} enjoys low runtime and memory overheads and offers insights into inefficiencies that no other tools can find.
86
87
88Remix~\cite{remix}, similar to \jxperf{}, also utilized PMU; while \jxperf{} identifies intra-thread inefficiencies, such as redundant/useless operations,  Remix dentifies false sharing across threads.
89%resource waste system resources such as CPU cycles wasted by poor algorithms and memory space wasted by inefficient object allocation patterns. However, these tools are based on exhaustive byte code instrumentation, which (1) incur high overhead (as high as 200$\times$ runtime overhead reported in~\cite{Nguyen:2013:CDC:2491411.2491416}), (2) are blind to the JITted binary code and (3) may change the JITted binary code a lot.
90
91
92%Similar to \jxperf{}, Remix~\cite{remix} utilizes PMUs to identify false sharing across Java threads, which is orthogonal to \jxperf{}, which identifies inefficiencies, such as redundant/useless operations inside each thread.
93%resource waste system resources such as CPU cycles wasted by poor algorithms and memory space wasted by inefficient object allocation patterns. However, these tools are based on exhaustive byte code instrumentation, which (1) incur high overhead (as high as 200$\times$ runtime overhead reported in~\cite{Nguyen:2013:CDC:2491411.2491416}), (2) are blind to the JITted binary code and (3) may change the JITted binary code a lot.
94
95
96\section{Background}
97\label{sec:background}
98
99\paragraph{\textbf{\textit{Hardware Performance Monitoring Units (PMU)}}}
100Modern CPUs expose programmable registers that count various hardware events such as memory loads, stores, CPU cycles, and many others.
101The registers can be configured in sampling mode: when a threshold number of hardware events elapse, PMUs trigger an overflow interrupt.
102A profiler is able to capture the interrupt as a signal, known as a sample, and attribute the metrics collected along with the sample to the execution context. PMUs are per CPU core and virtualized by the operating system (OS) for each thread.
103
104Intel offers Precise Event-Based Sampling (PEBS)~\cite{pmu-WWW} in SandyBridge and following generations.
105PEBS provides the effective address (EA) at the time of sample when the sample is for a  memory access instruction such as a load or a store.
106This facility is often referred to as address sampling, which is a critical building block of \jxperf{}.
107Also, PEBS can capture the precise instruction pointer (IP) for the instruction resulting in the counter overflow.
108AMD Instruction-Based Sampling (IBS)~\cite{AMDIBS:07} and PowerPC Marked Events (MRK)~\cite{Srinivas:2011:IBMJ-Power7} offer similar capabilities.
109
110\paragraph{\textbf{\textit{Hardware Debug Registers.}}}
111Hardware debug registers~\cite{Johnson:1982:RAS:800050.801837, McLear:1982:GCD:800050.801833} trap the CPU execution for debugging when the program counter (PC) reaches an address (breakpoint) or an instruction accesses a designated address (watchpoint). One can program debug registers to trap on various conditions: accessing addresses, accessing widths (1, 2, 4, 8 bytes), and accessing types (\wtrap{} and \rwtrap{}). The number of hardware debug registers is limited; modern x86 processors have only four debug registers.
112
113\paragraph{\textbf{\textit{Linux perf\_event.}}}
114Linux offers a standard interface to program PMUs and debug registers via the \texttt{perf\_event\_open} system call~\cite{perfevent} and the associated \texttt{ioctl} calls.
115%The Linux kernel can deliver a signal to the specific thread whose PMU event overflows or debug register traps.
116A watchpoint exception is a synchronous CPU trap caused when an instruction accesses a monitored address,
117while a PMU sample is a CPU interrupt caused when an event counter overflows.
118Both PMU samples and watchpoint exceptions are handled via Linux signals.
119The user code can (1) mmap a circular buffer to which the kernel keeps appending the PMU data on each sample and (2) extract the signal context on each debug register trap.
120
121
122
123\paragraph{\textbf{\textit{Java Virtual Machine Tool Interface (JVMTI)}}}
124\sloppy
125JVMTI~\cite{jvmti-WWW} is a native programming interface of the JVM. A JVMTI client can develop a debugger/profiler (aka JVMTI agent) in any C/C++ based native language to inspect the state and control the execution of JVM-based programs. JVMTI provides a number of event callbacks to capture JVM initialization and death, thread creation and destruction, method loading and unloading, garbage collection start and end, to name a few. User-defined functions are registered in these callbacks and invoked when the associated events happen. In addition, JVMTI maintains a variety of information for queries, such as the map from the machine code of each JITted method to byte code and source code,  and the call path for any given point during the execution. JVMTI is available in off-the-shelf Oracle HotSpot JVM.
126
127
128\section{Methodology}
129\label{sec:methodology}
130
131In the context of this paper, we define the following three kinds of wasteful memory accesses.
132
133\begin{definition}[Dead store]
134$S_1$ and $S_2$ are two successive memory stores to location $M$ ($S_1$ occurs before $S_2$). $S_1$ is a dead store iff there are no intervening memory loads from $M$ between $S_1$ and $S_2$. In such a case, we call $\langle S_1, S_2\rangle$ a dead store pair.
135\end{definition}
136
137\begin{definition}[Slient store]
138A memory store $S_2$, storing a value $V_2$ to location $M$, is a silent store iff the previous memory store $S_1$  performed on $M$ stored a value $V_1$, and $V_1 = V_2$. In such a case, we call $\langle S_1, S_2\rangle$ a silent store pair.
139\end{definition}
140
141\begin{definition}[Silent load]
142A memory load $L_2$, loading a value $V_2$ from location $M$ is a silent load iff the previous memory load $L_1$  performed on $M$ loaded a value $V_1$, and $V_1 = V_2$. In such a case, we call $\langle L_1, L_2\rangle$ a silent load pair.
143\end{definition}
144
145Silent stores and silent loads are value-aware inefficiencies whereas dead stores are value-agnostic ones.
146We perform precise equality check on integer values, and approximate equality check on floating-point (FP) values within a user-specified threshold of difference (1\% by default), given the fact that all FP numbers under the IEEE 754 format are approximately represented in the machine. For memory operations involved in the inefficiencies, we typically use their calling contexts instead of their effective addresses to represent them, which can facilitate optimization efforts.
147
148Figure~\ref{fig:scheme} highlights the idea of \jxperf{} in detecting inefficiencies at runtime, exemplified with silent stores.
149PMU drives \jxperf{} by sampling precise memory stores.
150%A PMU sample delivers an interrupt along with the effective address the program was accessing at the point of execution.
151For a sampled store operation, \jxperf{} records the effective address captured by the PMU, reads the value in this address,  and sets up a trap-on-store watchpoint on this address via the debug register.
152The subsequent store to the same effective address in the execution will trap.
153\jxperf{} captures the trap and checks the value at the effective address of the trap.
154If the value remains unchanged between the two consecutive accesses, \jxperf{} reports a pair of silent stores.
155The watchpoint is disabled, and the execution continues as usual to detect more such instances.
156%We elaborate on the implementation details of \jxperf{} in the context of Java in the next section.
157
158\begin{comment}
159uses the effective address to arm a hardware debug register to monitor the location for a subsequent access to the same location by the same OS thread.
160With the help of JVMTI, \jxperf{} associates measurement results with both JITted binary code and source code within their full calling contexts.
161Measurements accumulate throughout execution and get attributed to calling context pairs maintained compact calling context tree~\cite{Ammons:1997:EHP:258915.258924} data structure.
162On program termination, the measurements are processed and presented in their priority order of how frequently they occur in the same calling context pairs.
163Guidance provided by \jxperf{} helps eliminate inefficiencies in the binary code generation as well as in the program semantics such as data structures and algorithms.
164\end{comment}
165
166\begin{figure}[t]
167\begin{center}
168\includegraphics[width=0.48\textwidth]{scheme.pdf}
169\end{center}
170\caption{\jxperf{}’s scheme for silent store detection. \textcircled{1} The PMU samples a memory store $S_1$ that touches location $M$. \textcircled{2} In the PMU sample handler, a debug register is armed to monitor subsequent access to $M$. \textcircled{3} The debug register traps on the next store $S_2$ to $M$. \textcircled{4} If $S_1$ and $S_2$ write the same values to $M$, \jxperf{} labels $S_2$ as a silent store and $\langle S_1, S_2\rangle$ as a silent store pair.}
171\label{fig:scheme}
172\vspace{-1em}
173\end{figure}
174
175
176\section{Design and Implementation}
177\label{sec:design}
178
179%As a native JVMTI agent, \jxperf{} is dynamically linked into the HotSpot JVM at runtime. The overview of \jxperf{} is shown in Figure~\ref{fig:overview}.
180Figure~\ref{fig:overview} overviews \jxperf{} in the entire system stack.
181\jxperf{} requires no modification to hardware (x86), OS (Linux), JVM (Oracle HotSpot), and monitored Java applications. In this section, we first describe the implementation details of \jxperf{} in identifying wasteful memory operations, then show how \jxperf{} addresses the challenges, and finally depict how \jxperf{} provides extra information to guide code optimization.
182\jxperf{} generates per-thread profiles and merges them to provide an aggregate view as the output.
183
184\begin{figure}[t]
185\begin{center}
186\includegraphics[width=0.33\textwidth]{overview.pdf}
187\end{center}
188\caption{Overview of \jxperf{} in the system stack.}
189\label{fig:overview}
190\vspace{-1em}
191\end{figure}
192
193
194\subsection{Lightweight Inefficiency Detection}
195\label{sec:lightweight}
196
197%We elaborate the implementation details of \jxperf{} in detecting the three types of inefficiencies with lightweight PMUs and debug registers profiling within the support of JVMTI callbacks.
198
199\paragraph{\textbf{\textit{Silent Store Detection.}}}
200\begin{enumerate}
201\item \jxperf{} subscribes to the precise PMU store event at the JVM initialization callbacks and sets up PMUs and debug registers for each thread via \texttt{perf\_event} API in the JVMTI thread creation callback.
202\item When a PMU counter overflows during the execution, it triggers an interrupt.
203 \jxperf{} captures the interrupt, constructs the calling context $C_1$ of the interrupt, and extracts the effective address $M$ and the value $V_1$ stored at $M$.
204\item  \jxperf{} sets a \wtrap{} (trap-on-store) watchpoint on $M$ and resumes the program execution.
205\item A subsequent store to $M$  triggers a trap.  \jxperf{} handles the trap signal, constructs the calling context $C_2$ of the trap, and inspects the value $V_2$ stored at $M$.
206\item  \jxperf{} compares $V_1$ and $V_2$.
207If $V_1 = V_2$, a silent store is detected, and \jxperf{} labels the context pair $\langle C_1, C_2\rangle$ as an instance of silent store pair.
208\item \jxperf{}  disarms the debug register and resumes execution.
209\end{enumerate}
210
211\paragraph{\textbf{\textit{Dead Store Detection.}}}
212\jxperf{} subscribes to the precise PMU store event for dead store detection. When a PMU counter overflows, \jxperf{} constructs the calling context $C_1$ of the interrupt, extracts the effective address $M$,  sets a \rwtrap{} (load and store) watchpoint on $M$, and resumes program execution.
213When the subsequent access traps, \jxperf{} examines the access type (store or load).
214If it is a store, \jxperf{} constructs the calling context $C_2$ of the trap and records the pair $\langle C_1, C_2\rangle$ as an instance of dead store pair.
215Otherwise, it is not a dead store.
216
217\paragraph{\textbf{\textit{Silent Load Detection.}}}
218The detection is similar to the silent store detection, except that \jxperf{} subscribes to the precise PMU load event and sets a \rwtrap{} watchpoint~\footnote{x86 debug registers do not offer trap-only-on-load facility.} to trap the subsequent access to the same memory address.
219If the watchpoint triggers on a load that reads the same value as the previous load from the same location, \jxperf{}  reports an instance of silent load pair.
220
221
222The following metrics compute the fraction of wasteful memory operations in an execution:
223\begin{eqnarray}
224\scriptsize
225\begin{aligned}
226{\mathcal F}_{prog}^{DeadStore}=&{\sum_i\sum_j\text{Dead bytes stored in}\langle C_{i}, C_{j}\rangle \over \sum_i\sum_j\text{ Bytes stored in} \langle C_{i}, C_{j}\rangle} \\
227{\mathcal F}_{prog}^{SilentStore}=&{\sum_i\sum_j\text{Silent bytes stored in}\langle C_{i}, C_{j}\rangle \over \sum_i\sum_j\text{ Bytes stored in} \langle C_{i}, C_{j}\rangle } \\
228{\mathcal F}_{prog}^{SilentLoad}=&{\sum_i\sum_j\text{Silent bytes loaded from}\langle C_{i}, C_{j}\rangle \over \sum_i\sum_j\text{Bytes loaded from} \langle C_{i}, C_{j}\rangle}
229\end{aligned}
230\end{eqnarray}
231
232Fraction of wasteful memory operations in a calling context pair is given as follows:
233\begin{eqnarray}
234\scriptsize
235\begin{aligned}
236{\mathcal F}_{\langle C_{watch}, C_{trap}\rangle}^{DeadStore}=&{\text{Dead bytes stored in}\langle C_{watch}, C_{trap}\rangle \over \sum_i\sum_j\text{Bytes stored in} \langle C_{i}, C_{j}\rangle}  \\
237{\mathcal F}_{\langle C_{watch}, C_{trap}\rangle}^{SilentStore}=&{\text{Silent bytes stored in}\langle C_{watch}, C_{trap}\rangle \over \sum_i\sum_j\text{Bytes stored in} \langle C_{i}, C_{j}\rangle}  \\
238{\mathcal F}_{\langle C_{watch}, C_{trap}\rangle}^{SilentLoad}=&{\text{Silent bytes loaded from}\langle C_{watch}, C_{trap}\rangle \over \sum_i\sum_j\text{Bytes loaded from} \langle C_{i}, C_{j}\rangle}
239\end{aligned}
240\end{eqnarray}
241
242
243\subsection{Limited Number of Debug Registers}
244\label{sec:limited}
245
246Hardware offers a small number of debug registers, which becomes a limitation if the PMU delivers a new sample before a previously set watchpoint traps.
247To better understand the problem, consider the silent load example in Listing~\ref{lst:longDist}.
248Assume the loop indices \texttt{i} and \texttt{j}, and the scalars \texttt{sum1} and \texttt{sum2} are in registers.
249Further assume the PMU is configured to deliver a sample every 1K memory loads and the number of debug register is only one.
250The first sample occurs in loop $i$ when accessing \texttt{array[1K]}, which results in setting a watchpoint to monitor the address of \texttt{array[1K]}.
251The second sample occurs when accessing \texttt{array[2K]}.
252Since the watchpoint armed at \texttt{array[1K]} is still active, we should either forgo monitoring it in favor of  \texttt{array[2K]} or ignore the new sample.
253The former choice allows us to potentially detect a pair of silent loads separated by many intervening loads, and the latter choice allows us to detect a pair of silent loads separated by only a few intervening loads.
254The option is not obvious without looking into the future.
255A naive ‘‘\emph{replace the oldest policy}’’ is futile as it will not detect a single silent load in the above example.
256Even a slightly smart \emph{exponential decay} strategy will not work because the survival probability of an old watchpoint will be minuscule over many samples.
257
258\jxperf{} employs reservoir sampling~\cite{Vitter:1985:RSR:3147.3165,witch,rdx}, which uniformly chooses between old and new samples with no temporal bias.
259The first sampled address $M_1$, occupies the debug register with 1.0 probability.
260The second sampled address $M_2$, occupies the previously armed watchpoint with $\sfrac{1}{2}$ probability and retains $M_1$ with $\sfrac{1}{2}$ probability.
261The third sampled address $M_3$, either occupies the previously armed watchpoint with $\sfrac{1}{3}$ probability or retains it ($M_1$ or $M_2$) with $\sfrac{2}{3}$ probability.
262The $i^{th}$ sampled address $M_i$ since the last time a debug register was available, replaces the previously armed watchpoint with $\sfrac{1}{i}$ probability.
263The probability $P_k$ of monitoring any sampled address $M_k$, $1\le k \le i$, is the same ($\sfrac{1}{i}$), ensuring uniform sampling over time.
264When a watchpoint exception occurs, \jxperf{} disarms that watchpoint and resets its reservoir (replacement) probability to 1.0.
265Obviously, with this scheme \jxperf{} does not miss any sample if every watchpoint traps before being replaced.
266
267The scheme trivially extends to more number of debug registers, say $N \ge 1$.
268\jxperf{} maintains an independent reservoir probability $P_\alpha$  for each debug register $\alpha$, ($1\le \alpha \le N$).
269On a PMU sample, if there is an available debug register, \jxperf{} arms it and decrements the reservoir probability of other already-armed debug registers; otherwise \jxperf{} visits each debug register $\alpha$ and attempts to replace it with the probability $P_\alpha$.
270The process may succeed or fail in arming a debug register for a new sample, but it gives a new sample $N$ chances to remain in a system with $N$ watchpoints. Whether success or failure, $P_\alpha$ of each in-use debug register is updated after a sample.
271The order of visiting the debug registers is randomized for each sample to ensure fairness.
272Notice that this scheme maintains only a count of previous samples (not an access log), which consumes $\mathcal{O}(1)$  memory.
273
274\begin{figure}[t]
275\begin{lstlisting}[firstnumber=1,language=java]
276for (int i = 1; i <= 10K; i++) sum1 += array[i];
277for (int j = 1; j <= 10K; j++) sum2 += array[j]; // silent loads
1\vspace{-1em}
2\end{figure}
3
4
5\subsection{Interference of the Garbage Collector}
6Garbage collection (GC) can move live objects from one memory location to another memory location.
7Without paying heed to GC events, \jxperf{} can introduce two kinds of errors: (1) it may erroneously attribute an instance of inefficiency (e.g., dead store) to a location that is in reality occupied by two different objects between two consecutive accesses by the same thread; (2) it may miss attributing an inefficiency metric to an object because it was moved from one memory location to another between two consecutive accesses by the same thread.
8
9If \jxperf{} were able to query the garbage collector for moved objects or addresses, it could have avoided such errors, however, no such facility exists to the best of our knowledge in commercial JVMs.
10\jxperf{}’s solution is to monitor accesses only between GC epochs.
11\jxperf{} captures the start and end points of GC by registering the JVMTI callbacks \texttt{GarbageCollectionStart} and \texttt{GarbageCollectionFinish} to demarcate epochs.
12Watchpoints armed in an older epoch are not carried over to a new epoch: the first PMU sample or watchpoint trap that happens in a thread in a new epoch disarms all active watchpoints in that thread and begins afresh with a reservoir sampling probability of 1.0 for all debug registers for that thread.
13Note that the GC thread is never monitored.
14Typically, two consecutive accesses separated by a GC is infrequent; for example, the ratio of $\frac{\text{\# of GCs}}{\text{\# of PMU samples}}$ is 4.4e-5 in Dacapo-9.12-MR1-bach eclipse~\cite{Blackburn:2006:DBJ:1167473.1167488}.
15
16
17\subsection {Attributing Measurement to Binary}
18\label{sec:binary}
19\jxperf{} uses Intel XED library~\cite{XED-WWW} for on-the-fly disassembly of JITed methods.
20\jxperf{} retains the disassembly for post-mortem inspection if desired.
21It also uses XED to determine whether a watchpoint trap was caused by a load or a store instruction.
22
23A subtle implementation issue is in extracting the instruction that causes the watchpoint trap.
24\jxperf{} uses the \texttt{perf\_event} API to register a \texttt{HW\_BREAKPOINT} perf event (watchpoint event) for a monitored address.
25Although the watchpoint causes a trap immediately after the instruction execution, the instruction pointer (IP) seen in the signal handler context (contextIP) is one ahead of the actual IP (trapIP) that triggers the trap.
26In the x86 variable-length instruction set, it is nontrivial to derive the trapIP, even though it is just one instruction before the contextIP.
27The \texttt{HW\_BREAKPOINT} event in \texttt{perf\_event} is not a PMU event; hence, the Intel PEBS support, which otherwise provides the precise register state, is unavailable for a watchpoint.
28\jxperf{} disassembles every instruction from the method beginning till it reaches the IP just before the contextIP.
29The expensive disassembly is amortized by caching results for subsequent traps that often happen at the same IP.
30The caching is particularly important in methods with a large body; for example, when detecting silent loads in Dacapo-9.12-MR1-bach eclipse, without caching \jxperf{} introduces 4$\times$ runtime overhead.
31
32\subsection{Attributing Measurement to Source Code}
33\label{sec:cct}
34
35\sloppy
36Attributing runtime statistics to a flat profile (just an instruction) does not provide the full details needed for developer action.
37For example, attributing inefficiencies to a common JDK method, e.g., \texttt{string.equals()}, offers little insight since \texttt{string.equals()} can be invoked from several places in a large code base; some invocations may not even be obvious to the user code. A