Skip to content

Commit 8ea6a87

Browse files
Merge pull request #5800 from kwvanderlinde/performance/5795-timer-improvements
Timer improvements
2 parents e7612f4 + 42cbb6d commit 8ea6a87

16 files changed

Lines changed: 233 additions & 151 deletions

common/src/main/java/net/rptools/lib/StringUtil.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.Arrays;
2121
import java.util.List;
22+
import java.util.concurrent.TimeUnit;
2223
import java.util.regex.Pattern;
2324

2425
/**
@@ -28,6 +29,18 @@ public class StringUtil {
2829
private static NumberFormat nf = NumberFormat.getNumberInstance();
2930
private static final int MIN_FRACTION_DIGITS = 0;
3031

32+
public static String formatTimeUnit(TimeUnit unit) {
33+
return switch (unit) {
34+
case NANOSECONDS -> "ns";
35+
case MICROSECONDS -> "μs";
36+
case MILLISECONDS -> "ms";
37+
case SECONDS -> "s";
38+
case MINUTES -> "m";
39+
case HOURS -> "h";
40+
case DAYS -> "d";
41+
};
42+
}
43+
3144
public static String formatDecimal(double value) {
3245
String result1;
3346
result1 = nf.format(value); // On a separate line to allow for breakpoints

src/main/java/net/rptools/lib/CodeTimer.java

Lines changed: 102 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,23 @@
1414
*/
1515
package net.rptools.lib;
1616

17+
import java.awt.EventQueue;
1718
import java.util.ArrayList;
1819
import java.util.LinkedHashMap;
1920
import java.util.List;
2021
import java.util.Map;
22+
import java.util.concurrent.TimeUnit;
2123
import net.rptools.maptool.client.AppState;
2224
import net.rptools.maptool.client.MapTool;
25+
import org.apache.logging.log4j.LogManager;
26+
import org.apache.logging.log4j.Logger;
2327

2428
public class CodeTimer {
2529
private static final ThreadLocal<CodeTimer> ROOT_TIMER =
2630
ThreadLocal.withInitial(() -> new CodeTimer(""));
2731
private static final ThreadLocal<List<CodeTimer>> timerStack =
2832
ThreadLocal.withInitial(ArrayList::new);
33+
private static final Logger log = LogManager.getLogger(CodeTimer.class);
2934

3035
@FunctionalInterface
3136
public interface TimedSection<Ex extends Throwable> {
@@ -49,11 +54,7 @@ public static <Ex extends Exception> void using(String name, TimedSection<Ex> ca
4954
final var lastTimer = stack.removeLast();
5055
assert lastTimer == timer : "Timer stack is corrupted";
5156

52-
if (timer.isEnabled()) {
53-
String results = timer.toString();
54-
MapTool.getProfilingNoteFrame().addText(results);
55-
}
56-
timer.clear();
57+
timer.complete();
5758
}
5859
}
5960

@@ -62,23 +63,38 @@ public static CodeTimer get() {
6263
return stack.isEmpty() ? ROOT_TIMER.get() : stack.getLast();
6364
}
6465

65-
private final Map<String, Integer> counterMap = new LinkedHashMap<>();
66-
private final Map<String, Timer> timeMap = new LinkedHashMap<>();
66+
private enum TimerEventType {
67+
Start,
68+
Stop
69+
}
70+
71+
private record TimerEvent(TimerEventType type, long timeNs, String id, Object[] parameters) {}
72+
73+
private record CounterEvent(long amount, String id) {}
74+
75+
private ArrayList<TimerEvent> timerEvents = new ArrayList<>(100);
76+
private ArrayList<CounterEvent> counterEvents = new ArrayList<>(100);
77+
6778
private final String name;
6879
private long threshold = 1;
80+
private TimeUnit reportingUnit = TimeUnit.MILLISECONDS;
6981
private boolean enabled;
7082

71-
public CodeTimer(String n) {
83+
private CodeTimer(String n) {
7284
name = n;
7385
enabled = true;
7486
}
7587

76-
public boolean isEnabled() {
77-
return enabled;
88+
public void setThreshold(long threshold) {
89+
this.threshold = TimeUnit.NANOSECONDS.convert(threshold, TimeUnit.MILLISECONDS);
7890
}
7991

80-
public void setThreshold(long threshold) {
81-
this.threshold = threshold;
92+
public void setThreshold(long threshold, TimeUnit timeUnit) {
93+
this.threshold = TimeUnit.NANOSECONDS.convert(threshold, timeUnit);
94+
}
95+
96+
public void setReportingUnit(TimeUnit reportingUnit) {
97+
this.reportingUnit = reportingUnit;
8298
}
8399

84100
public void setEnabled(boolean enabled) {
@@ -90,62 +106,95 @@ public void increment(String id) {
90106
}
91107

92108
public void increment(String id, int amount) {
93-
counterMap.merge(id, amount, Integer::sum);
109+
if (!enabled) {
110+
return;
111+
}
112+
counterEvents.add(new CounterEvent(amount, id));
94113
}
95114

96115
public void start(String id, Object... parameters) {
97116
if (!enabled) {
98117
return;
99118
}
100-
if (parameters.length > 0) {
101-
id = String.format(id, parameters);
102-
}
103-
104-
Timer timer = timeMap.computeIfAbsent(id, key -> new Timer());
105-
timer.start();
119+
timerEvents.add(new TimerEvent(TimerEventType.Start, System.nanoTime(), id, parameters));
106120
}
107121

108122
public void stop(String id, Object... parameters) {
109123
if (!enabled) {
110124
return;
111125
}
112-
if (parameters.length > 0) {
113-
id = String.format(id, parameters);
114-
}
126+
timerEvents.add(new TimerEvent(TimerEventType.Stop, System.nanoTime(), id, parameters));
127+
}
115128

116-
Timer timer = timeMap.get(id);
117-
if (timer == null) {
118-
throw new IllegalArgumentException("Could not find timer id: " + id);
129+
private void complete() {
130+
if (!enabled) {
131+
return;
119132
}
120-
timer.stop();
121-
}
122133

123-
public void clear() {
124-
timeMap.clear();
125-
counterMap.clear();
134+
var name = this.name;
135+
var reportingUnit = this.reportingUnit;
136+
var threshold = this.threshold;
137+
var timerEvents = this.timerEvents;
138+
var counterEvents = this.counterEvents;
139+
this.timerEvents = new ArrayList<>();
140+
this.counterEvents = new ArrayList<>();
141+
142+
EventQueue.invokeLater(
143+
() -> {
144+
String results = getResults(name, reportingUnit, threshold, timerEvents, counterEvents);
145+
MapTool.getProfilingNoteFrame().addText(results);
146+
});
126147
}
127148

128-
@Override
129-
public String toString() {
130-
StringBuilder builder = new StringBuilder(100);
149+
private static String getResults(
150+
String name,
151+
TimeUnit reportingUnit,
152+
long threshold,
153+
List<TimerEvent> timerEvents,
154+
List<CounterEvent> counterEvents) {
155+
final Map<String, Long> counterMap = new LinkedHashMap<>();
156+
// Maps timer names to start times.
157+
final Map<String, Timer> timeMap = new LinkedHashMap<>();
158+
159+
// Count up all the timing events.
160+
for (var event : timerEvents) {
161+
var id = String.format(event.id(), event.parameters());
162+
var timer = timeMap.computeIfAbsent(id, Timer::new);
163+
switch (event.type()) {
164+
case Start -> timer.startAt(event.timeNs());
165+
case Stop -> timer.stopAt(event.timeNs());
166+
}
167+
}
131168

169+
// Count up all the counter events.
170+
for (var event : counterEvents) {
171+
counterMap.merge(event.id(), event.amount(), Long::sum);
172+
}
173+
174+
StringBuilder builder = new StringBuilder(100);
132175
builder
133176
.append("Timer ")
134177
.append(name)
135178
.append(" (")
136179
.append(timeMap.size())
137180
.append(" elements)\n");
138-
139181
var i = -1;
140182
for (var entry : timeMap.entrySet()) {
141183
++i;
142184

143185
var id = entry.getKey();
144-
long elapsed = entry.getValue().getElapsed() / 1_000_000;
186+
long elapsed = entry.getValue().elapsedNs;
145187
if (elapsed < threshold) {
146188
continue;
147189
}
148-
builder.append(String.format(" %3d. %6d ms %s\n", i, elapsed, id));
190+
191+
builder.append(
192+
String.format(
193+
" %3d. %6d %s %s\n",
194+
i,
195+
reportingUnit.convert(elapsed, TimeUnit.NANOSECONDS),
196+
StringUtil.formatTimeUnit(reportingUnit),
197+
id));
149198
}
150199

151200
if (!counterMap.isEmpty()) {
@@ -164,29 +213,29 @@ public String toString() {
164213
return builder.toString();
165214
}
166215

167-
private static class Timer {
168-
long elapsed;
169-
long start = -1;
216+
private static final class Timer {
217+
String name;
218+
long elapsedNs = 0;
219+
long startTimeNs = -1;
170220

171-
private long getTime() {
172-
return System.nanoTime();
221+
public Timer(String name) {
222+
this.name = name;
173223
}
174224

175-
public void start() {
176-
start = getTime();
177-
}
178-
179-
public void stop() {
180-
elapsed += (getTime() - start);
181-
start = -1;
225+
public void startAt(long timeNs) {
226+
if (startTimeNs >= 0) {
227+
log.warn("Invalid timer state: attempted to start timer {} that was already started", name);
228+
}
229+
startTimeNs = timeNs;
182230
}
183231

184-
public long getElapsed() {
185-
long time = elapsed;
186-
if (start > 0) {
187-
time += (getTime() - start);
232+
public void stopAt(long timeNs) {
233+
if (startTimeNs < 0) {
234+
log.warn("Invalid timer state: attempted to stop timer {} that was not started", name);
235+
return;
188236
}
189-
return time;
237+
elapsedNs += (timeNs - startTimeNs);
238+
startTimeNs = -1;
190239
}
191240
}
192241
}

src/main/java/net/rptools/maptool/client/swing/NoteFrame.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@
1717
import java.awt.BorderLayout;
1818
import java.awt.Color;
1919
import java.awt.Dimension;
20+
import java.awt.Font;
2021
import java.awt.event.WindowEvent;
2122
import javax.swing.BorderFactory;
2223
import javax.swing.JButton;
2324
import javax.swing.JFrame;
2425
import javax.swing.JPanel;
2526
import javax.swing.JScrollPane;
2627
import javax.swing.JTextArea;
27-
import javax.swing.text.JTextComponent;
2828
import net.rptools.maptool.language.I18N;
2929

3030
public class NoteFrame extends JFrame {
31-
private JTextComponent noteArea;
31+
private JTextArea noteArea;
3232
private JButton clearButton;
3333
private JButton closeButton;
3434

@@ -43,10 +43,12 @@ private void initUI() {
4343
add(BorderLayout.SOUTH, createButtonBar());
4444
}
4545

46-
private JTextComponent getNoteArea() {
46+
private JTextArea getNoteArea() {
4747
if (noteArea == null) {
4848
noteArea = new JTextArea();
4949
noteArea.setBorder(BorderFactory.createLineBorder(Color.black));
50+
noteArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, noteArea.getFont().getSize()));
51+
noteArea.setEditable(false);
5052
}
5153
return noteArea;
5254
}
@@ -78,7 +80,8 @@ private JButton getCloseButton() {
7880
}
7981

8082
public synchronized void addText(String text) {
81-
getNoteArea().setText(getNoteArea().getText() + text + "\n");
83+
getNoteArea().append(text);
84+
getNoteArea().append("\n");
8285
getNoteArea().setCaretPosition(getNoteArea().getText().length());
8386
}
8487
}

0 commit comments

Comments
 (0)