Skip to content

Commit af1823d

Browse files
authored
Reduce impact of tick time calculations (#13188)
1 parent b57d641 commit af1823d

File tree

3 files changed

+193
-112
lines changed

3 files changed

+193
-112
lines changed

paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
private int playerIdleTimeout;
4242
private final long[] tickTimesNanos = new long[100];
4343
private long aggregatedTickTimesNanos = 0L;
44-
@@ -278,10 +_,91 @@
44+
@@ -278,10 +_,108 @@
4545
private final DiscontinuousFrame tickFrame;
4646
private final PacketProcessor packetProcessor;
4747

@@ -70,7 +70,6 @@
7070
+ // Paper start - improve tick loop
7171
+ public final ca.spottedleaf.moonrise.common.time.TickData tickTimes1s = new ca.spottedleaf.moonrise.common.time.TickData(java.util.concurrent.TimeUnit.SECONDS.toNanos(1L));
7272
+ public final ca.spottedleaf.moonrise.common.time.TickData tickTimes5s = new ca.spottedleaf.moonrise.common.time.TickData(java.util.concurrent.TimeUnit.SECONDS.toNanos(5L));
73-
+ private volatile @Nullable ca.spottedleaf.moonrise.common.time.TickData.TickReportData tickReport5s;
7473
+ public final ca.spottedleaf.moonrise.common.time.TickData tickTimes10s = new ca.spottedleaf.moonrise.common.time.TickData(java.util.concurrent.TimeUnit.SECONDS.toNanos(10L));
7574
+ public final ca.spottedleaf.moonrise.common.time.TickData tickTimes15s = new ca.spottedleaf.moonrise.common.time.TickData(java.util.concurrent.TimeUnit.SECONDS.toNanos(15L));
7675
+ public final ca.spottedleaf.moonrise.common.time.TickData tickTimes1m = new ca.spottedleaf.moonrise.common.time.TickData(java.util.concurrent.TimeUnit.MINUTES.toNanos(1L));
@@ -82,45 +81,63 @@
8281
+ private long lastTickStart;
8382
+ private long currentTickStart;
8483
+ private long scheduledTickStart;
85-
+ private volatile double[] tps = new double[]{0.0, 0.0, 0.0};
84+
+ private final Object statsLock = new Object();
85+
+ private @Nullable double[] tps;
86+
+ private @Nullable ca.spottedleaf.moonrise.common.time.TickData.MSPTData msptData5s;
8687
+
8788
+ private void addTickTime(final ca.spottedleaf.moonrise.common.time.TickTime time) {
88-
+ org.spigotmc.AsyncCatcher.catchOp("addTickTime");
89-
+ this.tickTimes1s.addDataFrom(time);
90-
+ this.tickTimes5s.addDataFrom(time);
91-
+ this.tickReport5s = this.tickTimes5s.generateTickReport(null, System.nanoTime(), this.tickRateManager().nanosecondsPerTick());
92-
+ this.tickTimes10s.addDataFrom(time);
93-
+ this.tickTimes15s.addDataFrom(time);
94-
+ this.tickTimes1m.addDataFrom(time);
95-
+ this.tickTimes5m.addDataFrom(time);
96-
+ this.tickTimes15m.addDataFrom(time);
97-
+ this.tps = this.computeTPS();
89+
+ synchronized (this.statsLock) {
90+
+ this.tickTimes1s.addDataFrom(time);
91+
+ this.tickTimes5s.addDataFrom(time);
92+
+ this.tickTimes10s.addDataFrom(time);
93+
+ this.tickTimes15s.addDataFrom(time);
94+
+ this.tickTimes1m.addDataFrom(time);
95+
+ this.tickTimes5m.addDataFrom(time);
96+
+ this.tickTimes15m.addDataFrom(time);
97+
+ this.clearTickTimeStatistics();
98+
+ }
99+
+ }
100+
+
101+
+ private void clearTickTimeStatistics() {
102+
+ this.msptData5s = null;
103+
+ this.tps = null;
98104
+ }
99105
+
100-
+ private static double getTPS(final ca.spottedleaf.moonrise.common.time.TickData tickData, final long timeNow, final long tickInterval) {
101-
+ final ca.spottedleaf.moonrise.common.time.TickData.TickReportData tickReportData = tickData.generateTickReport(null, timeNow, tickInterval);
102-
+ if (tickReportData == null) {
106+
+ private static double getTPS(final ca.spottedleaf.moonrise.common.time.TickData tickData, final long tickInterval) {
107+
+ final Double avg = tickData.getTPSAverage(null, tickInterval);
108+
+ if (avg == null) {
103109
+ return 1.0E9 / (double)tickInterval;
104110
+ }
105111
+
106-
+ return tickReportData.tpsData().segmentAll().average();
112+
+ return avg;
107113
+ }
108114
+
109115
+ public double[] getTPS() {
110-
+ return this.tps.clone();
116+
+ synchronized (this.statsLock) {
117+
+ double[] tps = this.tps;
118+
+ if (tps == null) {
119+
+ tps = this.computeTPS();
120+
+ this.tps = tps;
121+
+ }
122+
+ return tps.clone();
123+
+ }
111124
+ }
112125
+
113-
+ public @Nullable ca.spottedleaf.moonrise.common.time.TickData.TickReportData getTickReport5s() {
114-
+ return this.tickReport5s;
126+
+ public @Nullable ca.spottedleaf.moonrise.common.time.TickData.MSPTData getMSPTData5s() {
127+
+ synchronized (this.statsLock) {
128+
+ if (this.msptData5s == null) {
129+
+ this.msptData5s = this.tickTimes5s.getMSPTData(null, this.tickRateManager().nanosecondsPerTick());
130+
+ }
131+
+ return this.msptData5s;
132+
+ }
115133
+ }
116134
+
117135
+ public double[] computeTPS() {
118-
+ final long now = System.nanoTime();
119136
+ final long interval = this.tickRateManager().nanosecondsPerTick();
120137
+ return new double[] {
121-
+ getTPS(this.tickTimes1m, now, interval),
122-
+ getTPS(this.tickTimes5m, now, interval),
123-
+ getTPS(this.tickTimes15m, now, interval)
138+
+ getTPS(this.tickTimes1m, interval),
139+
+ getTPS(this.tickTimes5m, interval),
140+
+ getTPS(this.tickTimes15m, interval)
124141
+ };
125142
+ }
126143
+ // Paper end - improve tick loop

paper-server/src/main/java/ca/spottedleaf/moonrise/common/time/TickData.java

Lines changed: 148 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,67 @@ private static record TickInformation(
103103
long tickTimeCPU
104104
) {}
105105

106+
public Double getTPSAverage(final TickTime inProgress, final long tickInterval) {
107+
return this.getTPSAverage(inProgress, tickInterval, false);
108+
}
109+
110+
public Double getTPSAverage(final TickTime inProgress, final long tickInterval, final boolean createFakeTick) {
111+
if (this.timeData.isEmpty() && inProgress == null) {
112+
return null;
113+
}
114+
115+
final List<TickTime> allData = new ArrayList<>(this.timeData);
116+
if (inProgress != null) {
117+
allData.add(inProgress);
118+
}
119+
120+
final List<TickInformation> collapsedData = collapseData(allData, tickInterval, createFakeTick);
121+
if (collapsedData.isEmpty()) {
122+
return null;
123+
}
124+
125+
long totalTimeBetweenTicks = 0L;
126+
int collectedTicks = 0;
127+
for (final TickInformation time : collapsedData) {
128+
totalTimeBetweenTicks += time.differenceFromLastTick();
129+
++collectedTicks;
130+
}
131+
return (double)collectedTicks / ((double)totalTimeBetweenTicks / 1.0E9);
132+
}
133+
134+
public record MSPTData(double avg, long[] rawData) {}
135+
136+
public MSPTData getMSPTData(final TickTime inProgress, final long tickInterval) {
137+
return this.getMSPTData(inProgress, tickInterval, false);
138+
}
139+
140+
public MSPTData getMSPTData(final TickTime inProgress, final long tickInterval, final boolean createFakeTick) {
141+
if (this.timeData.isEmpty() && inProgress == null) {
142+
return null;
143+
}
144+
145+
final List<TickTime> allData = new ArrayList<>(this.timeData);
146+
if (inProgress != null) {
147+
allData.add(inProgress);
148+
}
149+
150+
final List<TickInformation> collapsedData = collapseData(allData, tickInterval, createFakeTick);
151+
if (collapsedData.isEmpty()) {
152+
return null;
153+
}
154+
155+
long totalTimeTicking = 0L;
156+
int collectedTicks = 0;
157+
final long[] timePerTickDataRaw = new long[collapsedData.size()];
158+
for (int i = 0; i < collapsedData.size(); ++i) {
159+
final TickInformation time = collapsedData.get(i);
160+
totalTimeTicking += time.tickTime();
161+
timePerTickDataRaw[i] = time.tickTime();
162+
++collectedTicks;
163+
}
164+
return new MSPTData((double)totalTimeTicking / (double)collectedTicks * 1.0E-6, timePerTickDataRaw);
165+
}
166+
106167
public TickReportData generateTickReport(final TickTime inProgress, final long endTime, final long tickInterval) {
107168
return this.generateTickReport(inProgress, endTime, tickInterval, false);
108169
}
@@ -118,6 +179,11 @@ public TickReportData generateTickReport(final TickTime inProgress, final long e
118179
allData.add(inProgress);
119180
}
120181

182+
final List<TickInformation> collapsedData = collapseData(allData, tickInterval, createFakeTick);
183+
if (collapsedData.isEmpty()) {
184+
return null;
185+
}
186+
121187
final long intervalStart = allData.get(0).tickStart();
122188
final long intervalEnd = allData.get(allData.size() - 1).tickEnd();
123189

@@ -139,90 +205,6 @@ public TickReportData generateTickReport(final TickTime inProgress, final long e
139205
}
140206
}
141207

142-
// we only care about ticks, but because of inbetween tick task execution
143-
// there will be data in allData that isn't ticks. But, that data cannot
144-
// be ignored since it contributes to utilisation.
145-
// So, we will "compact" the data by merging any inbetween tick times
146-
// the next tick.
147-
// If there is no "next tick", then we will create one.
148-
final List<TickInformation> collapsedData = new ArrayList<>();
149-
for (int i = 0, len = allData.size(); i < len; ++i) {
150-
final List<TickTime> toCollapse = new ArrayList<>();
151-
TickTime lastTick = null;
152-
for (;i < len; ++i) {
153-
final TickTime time = allData.get(i);
154-
if (!time.isTickExecution()) {
155-
toCollapse.add(time);
156-
continue;
157-
}
158-
lastTick = time;
159-
break;
160-
}
161-
162-
if (toCollapse.isEmpty()) {
163-
// nothing to collapse
164-
final TickTime last = allData.get(i);
165-
collapsedData.add(
166-
new TickInformation(
167-
last.differenceFromLastTick(tickInterval),
168-
last.tickLength(),
169-
last.supportCPUTime() ? last.tickCpuTime() : 0L
170-
)
171-
);
172-
} else {
173-
long totalTickTime = 0L;
174-
long totalCpuTime = 0L;
175-
for (int k = 0, len2 = toCollapse.size(); k < len2; ++k) {
176-
final TickTime time = toCollapse.get(k);
177-
totalTickTime += time.tickLength();
178-
totalCpuTime += time.supportCPUTime() ? time.tickCpuTime() : 0L;
179-
}
180-
if (i < len) {
181-
// we know there is a tick to collapse into
182-
final TickTime last = allData.get(i);
183-
collapsedData.add(
184-
new TickInformation(
185-
last.differenceFromLastTick(tickInterval),
186-
last.tickLength() + totalTickTime,
187-
(last.supportCPUTime() ? last.tickCpuTime() : 0L) + totalCpuTime
188-
)
189-
);
190-
} else if (createFakeTick) {
191-
// we do not have a tick to collapse into, so we must make one up
192-
// we will assume that the tick is "starting now" and ongoing
193-
194-
// compute difference between imaginary tick and last tick
195-
final long differenceBetweenTicks;
196-
if (lastTick != null) {
197-
// we have a last tick, use it
198-
differenceBetweenTicks = lastTick.tickStart();
199-
} else {
200-
// we don't have a last tick, so we must make one up that makes sense
201-
// if the current interval exceeds the max tick time, then use it
202-
203-
// Otherwise use the interval length.
204-
// This is how differenceFromLastTick() works on TickTime when there is no previous interval.
205-
differenceBetweenTicks = Math.max(
206-
tickInterval, totalTickTime
207-
);
208-
}
209-
210-
collapsedData.add(
211-
new TickInformation(
212-
differenceBetweenTicks,
213-
totalTickTime,
214-
totalCpuTime
215-
)
216-
);
217-
}
218-
}
219-
}
220-
221-
222-
if (collapsedData.isEmpty()) {
223-
return null;
224-
}
225-
226208
final int collectedTicks = collapsedData.size();
227209
final long[] tickStartToStartDifferences = new long[collectedTicks];
228210
final long[] timePerTickDataRaw = new long[collectedTicks];
@@ -303,6 +285,88 @@ public TickReportData generateTickReport(final TickTime inProgress, final long e
303285
);
304286
}
305287

288+
private static List<TickInformation> collapseData(final List<TickTime> allData, final long tickInterval, final boolean createFakeTick) {
289+
// we only care about ticks, but because of inbetween tick task execution
290+
// there will be data in allData that isn't ticks. But, that data cannot
291+
// be ignored since it contributes to utilisation.
292+
// So, we will "compact" the data by merging any inbetween tick times
293+
// the next tick.
294+
// If there is no "next tick", then we will create one.
295+
final List<TickInformation> collapsedData = new ArrayList<>();
296+
for (int i = 0, len = allData.size(); i < len; ++i) {
297+
final List<TickTime> toCollapse = new ArrayList<>();
298+
TickTime lastTick = null;
299+
for (;i < len; ++i) {
300+
final TickTime time = allData.get(i);
301+
if (!time.isTickExecution()) {
302+
toCollapse.add(time);
303+
continue;
304+
}
305+
lastTick = time;
306+
break;
307+
}
308+
309+
if (toCollapse.isEmpty()) {
310+
// nothing to collapse
311+
final TickTime last = allData.get(i);
312+
collapsedData.add(
313+
new TickInformation(
314+
last.differenceFromLastTick(tickInterval),
315+
last.tickLength(),
316+
last.supportCPUTime() ? last.tickCpuTime() : 0L
317+
)
318+
);
319+
} else {
320+
long totalTickTime = 0L;
321+
long totalCpuTime = 0L;
322+
for (int k = 0, len2 = toCollapse.size(); k < len2; ++k) {
323+
final TickTime time = toCollapse.get(k);
324+
totalTickTime += time.tickLength();
325+
totalCpuTime += time.supportCPUTime() ? time.tickCpuTime() : 0L;
326+
}
327+
if (i < len) {
328+
// we know there is a tick to collapse into
329+
final TickTime last = allData.get(i);
330+
collapsedData.add(
331+
new TickInformation(
332+
last.differenceFromLastTick(tickInterval),
333+
last.tickLength() + totalTickTime,
334+
(last.supportCPUTime() ? last.tickCpuTime() : 0L) + totalCpuTime
335+
)
336+
);
337+
} else if (createFakeTick) {
338+
// we do not have a tick to collapse into, so we must make one up
339+
// we will assume that the tick is "starting now" and ongoing
340+
341+
// compute difference between imaginary tick and last tick
342+
final long differenceBetweenTicks;
343+
if (lastTick != null) {
344+
// we have a last tick, use it
345+
differenceBetweenTicks = lastTick.tickStart();
346+
} else {
347+
// we don't have a last tick, so we must make one up that makes sense
348+
// if the current interval exceeds the max tick time, then use it
349+
350+
// Otherwise use the interval length.
351+
// This is how differenceFromLastTick() works on TickTime when there is no previous interval.
352+
differenceBetweenTicks = Math.max(
353+
tickInterval, totalTickTime
354+
);
355+
}
356+
357+
collapsedData.add(
358+
new TickInformation(
359+
differenceBetweenTicks,
360+
totalTickTime,
361+
totalCpuTime
362+
)
363+
);
364+
}
365+
}
366+
}
367+
return collapsedData;
368+
}
369+
306370
public static final record TickReportData(
307371
int collectedTicks,
308372
long collectedTickIntervalStart,

paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2695,14 +2695,14 @@ public UnsafeValues getUnsafe() {
26952695

26962696
@Override
26972697
public long[] getTickTimes() {
2698-
final TickData.TickReportData reportData = this.getServer().getTickReport5s();
2699-
return reportData == null ? new long[0] : reportData.timePerTickData().rawData().clone();
2698+
final TickData.MSPTData reportData = this.getServer().getMSPTData5s();
2699+
return reportData == null ? new long[0] : reportData.rawData().clone();
27002700
}
27012701

27022702
@Override
27032703
public double getAverageTickTime() {
2704-
final TickData.TickReportData reportData = this.getServer().getTickReport5s();
2705-
return reportData == null ? 0.0 : reportData.timePerTickData().segmentAll().average() * 1.0E-6D;
2704+
final TickData.MSPTData reportData = this.getServer().getMSPTData5s();
2705+
return reportData == null ? 0.0 : reportData.avg();
27062706
}
27072707

27082708
private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot() {

0 commit comments

Comments
 (0)