注意:有很多 JVM 实现,这个答案并没有声称已经测试了所有这些,也不是所有可能情况的一般性陈述。
根据https://www.bettercodebytes.com/the-cost-of-object-creation-in-java-including-garbage-collection/,当对象只存在于方法中时,可能没有对象创建的开销。这是因为 JIT 实际上并不实例化对象,而是直接执行包含的方法。
因此,以后也不需要垃圾收集。
结合问题的测试可以这样实现:
控制器:
final List<Function<Event, Mono<Void>>> triggerOtherMicroservices = Arrays.asList(
event -> Mono.empty(),
event -> Mono.empty(),
event -> Mono.empty()
);
final Mono<Void> mono = Mono
.defer(() -> Mono
.subscriberContext()
.<Event>map(context -> context.get("event"))
.flatMap(this::fetch)
)
.subscribeOn(Schedulers.parallel())
.flatMap(this::duplicate)
.flatMap(this::duplicate)
.flatMap(this::duplicate)
.flatMap(this::duplicate)
.thenMany(Mono
.subscriberContext()
.<Event>map(context -> context.get("event"))
.flatMapMany(event -> Flux
.fromIterable(triggerOtherMicroservices)
.flatMap(t -> t.apply(event))
)
)
.then();
@PostMapping("/event-prepared")
public Mono<Void> handle(@RequestBody @Validated Event event) throws NoSuchElementException {
return mono.subscriberContext(context -> context.put("event", event));
}
@PostMapping("/event-on-the-fly")
public Mono<Void> handleOld(@RequestBody @Validated Event event) throws NoSuchElementException {
return Mono
.defer(() -> fetch(event))
.subscribeOn(Schedulers.parallel())
.flatMap(this::duplicate)
.flatMap(this::duplicate)
.flatMap(this::duplicate)
.flatMap(this::duplicate)
.thenMany(Flux.fromIterable(triggerOtherMicroservices).flatMap(t -> t.apply(event)))
.then();
}
private Mono<Data> fetch(Event event) {
return Mono.just(new Data(event.timestamp));
}
private Mono<Data> duplicate(Data data) {
return Mono.just(new Data(data.a * 2));
}
数据:
long a;
public Data(long a) {
this.a = a;
}
@Override
public String toString() {
return "Data{" +
"a=" + a +
'}';
}
事件:
@JsonSerialize(using = EventSerializer.class)
public class Event {
UUID source;
long timestamp;
@JsonCreator
public Event(@JsonProperty("source") UUID source, @JsonProperty("timestamp") long timestamp) {
this.source = source;
this.timestamp = timestamp;
}
@Override
public String toString() {
return "Event{" +
"source=" + source +
", timestamp=" + timestamp +
'}';
}
}
事件序列化器:
public class EventSerializer extends StdSerializer<Event> {
public EventSerializer() {
this(null);
}
public EventSerializer(Class<Event> t) {
super(t);
}
@Override
public void serialize(Event value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("source", value.source.toString());
jsonGenerator.writeNumberField("timestamp", value.timestamp);
jsonGenerator.writeEndObject();
}
}
最后是测试本身:
@SpringBootTest
@AutoConfigureWebTestClient
class MonoAssemblyTimeTest {
@Autowired
private WebTestClient webTestClient;
final int number_of_requests = 500000;
@Test
void measureExecutionTime() throws IOException {
measureExecutionTime("on-the-fly");
measureExecutionTime("prepared");
}
private void measureExecutionTime(String testCase) throws IOException {
warmUp("/event-" + testCase);
final GCStatisticsDifferential gcStatistics = new GCStatisticsDifferential();
long[] duration = benchmark("/event-" + testCase);
StringBuilder output = new StringBuilder();
int plotPointsInterval = (int) Math.ceil((float) number_of_requests / 1000);
for (int i = 0; i < number_of_requests; i++) {
if (i % plotPointsInterval == 0) {
output.append(String.format("%d , %d %n", i, duration[i]));
}
}
Files.writeString(Paths.get(testCase + ".txt"), output.toString());
long totalDuration = LongStream.of(duration).sum();
System.out.println(testCase + " duration: " + totalDuration / 1000000 + " ms.");
System.out.println(testCase + " average: " + totalDuration / number_of_requests + " ns.");
System.out.println(testCase + ": " + gcStatistics.get());
}
private void warmUp(String path) {
UUID source = UUID.randomUUID();
IntStream.range(0, number_of_requests).forEach(i -> call(new Event(source, i), path));
System.out.println("done with warm-up for path: " + path);
}
private long[] benchmark(String path) {
long[] duration = new long[number_of_requests];
UUID source = UUID.randomUUID();
IntStream.range(0, number_of_requests).forEach(i -> {
long start = System.nanoTime();
call(new Event(source, i), path).returnResult().getResponseBody();
duration[i] = System.nanoTime() - start;
});
System.out.println("done with benchmark for path: " + path);
return duration;
}
private WebTestClient.BodySpec<Void, ?> call(Event event, String path) {
return webTestClient
.post()
.uri(path)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(event)
.exchange()
.expectBody(Void.class);
}
private static class GCStatisticsDifferential extends GCStatistics {
GCStatistics old = new GCStatistics(0, 0);
public GCStatisticsDifferential() {
super(0, 0);
calculateIncrementalGCStats();
}
public GCStatistics get() {
calculateIncrementalGCStats();
return this;
}
private void calculateIncrementalGCStats() {
long timeNew = 0;
long countNew = 0;
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
long count = gc.getCollectionCount();
if (count >= 0) {
countNew += count;
}
long time = gc.getCollectionTime();
if (time >= 0) {
timeNew += time;
}
}
time = timeNew - old.time;
count = countNew - old.count;
old = new GCStatistics(timeNew, countNew);
}
}
private static class GCStatistics {
long count, time;
public GCStatistics(long count, long time) {
this.count = count;
this.time = time;
}
@Override
public String toString() {
return "GCStatistics{" +
"count=" + count +
", time=" + time +
'}';
}
}
}
结果并不总是相同,但“即时”方法始终优于“准备”方法。另外,“on-the-fly”方法的垃圾回收量要少得多。
典型的结果如下:
完成路径的热身:/event-on-the-fly
完成路径基准测试:/event-on-the-fly
动态持续时间:42679 毫秒。
动态平均值:85358 ns。
即时:GCStatistics{count=29, time=128}
完成了路径的预热:/event-prepared
完成路径基准测试:/event-prepared
准备持续时间:44678 毫秒。
准备好的平均值:89357 ns。
准备:GCStatistics{count=86, time=67}
此结果是在 MacBook Pro(16 英寸,2019 年)、2.4 GHz 8 核 Intel Core i9、64 GB 2667 MHz DDR4 上完成的。
注意:仍然非常欢迎评论、更好的答案或...。