Skip to content

Commit

Permalink
Specialise context class
Browse files Browse the repository at this point in the history
A large amount of static analysis time is spent computing hashcodes for
maps. A significant performance improvement can be made by creating
specialised classes for the common cases of contexts with 0, 1, or 2
values.
  • Loading branch information
Henry Coles committed May 18, 2022
1 parent fbc3215 commit b209fc0
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private CombinedStatistics runAnalysis(Runtime runtime, long t0, EngineArguments
engine, args, allInterceptors());
this.timings.registerEnd(Timings.Stage.BUILD_MUTATION_TESTS);

LOG.info("Created " + tus.size() + " mutation test units");
LOG.info("Created " + tus.size() + " mutation test units" );

recordClassPath(history, coverageData);

Expand Down
55 changes: 14 additions & 41 deletions pitest-entry/src/main/java/org/pitest/sequence/Context.java
Original file line number Diff line number Diff line change
@@ -1,59 +1,32 @@
package org.pitest.sequence;

import java.util.IdentityHashMap;

import java.util.Map;
import java.util.Optional;

public final class Context {

private final boolean debug;
private final Map<Slot,Object> slots;

Context(Map<Slot,Object> slots, boolean debug) {
this.slots = slots;
this.debug = debug;
}
public interface Context {

public static Context start() {
static Context start() {
return start(false);
}

public static Context start(boolean debug) {
return new Context(new IdentityHashMap<>(), debug);
static Context start(boolean debug) {
if (debug) {
return EmptyContext.WITH_DEBUG;
}
return EmptyContext.WITHOUT_DEBUG;
}

public <S> Context store(SlotWrite<S> slot, S value) {
Map<Slot,Object> mutatedSlots = new IdentityHashMap<>(slots);
mutatedSlots.put(slot.slot(), value);
return new Context(mutatedSlots, debug);
}
<S> Context store(SlotWrite<S> slot, S value);

@SuppressWarnings("unchecked")
public <S> Optional<S> retrieve(SlotRead<S> slot) {
return Optional.ofNullable((S)slots.get(slot.slot()));
}
<S> Optional<S> retrieve(SlotRead<S> slot);

public <T> void debug(String msg, T t) {
if (this.debug) {
System.out.println(msg + " for " + t);
}
default boolean debug() {
return false;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Context)) {
return false;
default <T> void debug(String msg, T t) {
if (debug()) {
System.out.println(msg + " for " + t);
}
Context context = (Context) o;
return slots.equals(context.slots);
}

@Override
public int hashCode() {
return slots.hashCode();
}
}
60 changes: 60 additions & 0 deletions pitest-entry/src/main/java/org/pitest/sequence/Context1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.pitest.sequence;

import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
* Specialisation of context for single values
*/
final class Context1 implements Context {
private final boolean debug;
private final Slot<?> slot;
private final Object value;

Context1(Slot<?> slot, Object value, boolean debug) {
this.slot = slot;
this.value = value;
this.debug = debug;
}

@Override
public boolean debug() {
return debug;
}

@Override
public <S> Context store(SlotWrite<S> slot, S value) {
Map<Slot,Object> mutatedSlots = new IdentityHashMap<>();
mutatedSlots.put(this.slot, this.value);
mutatedSlots.put(slot.slot(), value);
return new Context2(this.slot, this.value, slot.slot(), value, debug);
}

@SuppressWarnings("unchecked")
@Override
public <S> Optional<S> retrieve(SlotRead<S> read) {
if (read.slot().equals(slot)) {
return (Optional<S>) Optional.ofNullable(value);
}
return Optional.empty();
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Context1)) {
return false;
}
Context1 context1 = (Context1) o;
return slot.equals(context1.slot) && Objects.equals(value, context1.value);
}

@Override
public int hashCode() {
return Objects.hash(slot, value);
}
}
70 changes: 70 additions & 0 deletions pitest-entry/src/main/java/org/pitest/sequence/Context2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.pitest.sequence;

import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
* Specialisation of context for two values
*/
final class Context2 implements Context {
private final boolean debug;
private final Slot<?> slot;
private final Object value;
private final Slot<?> slot2;
private final Object value2;

Context2(Slot<?> slot, Object value, Slot<?> slot2, Object value2, boolean debug) {
this.slot = slot;
this.value = value;
this.slot2 = slot2;
this.value2 = value2;
this.debug = debug;
}

@Override
public boolean debug() {
return debug;
}

@Override
public <S> Context store(SlotWrite<S> slot, S value) {
Map<Slot,Object> mutatedSlots = new IdentityHashMap<>();
mutatedSlots.put(this.slot, this.value);
mutatedSlots.put(this.slot2, this.value2);
mutatedSlots.put(slot.slot(), value);
return new MultiContext(mutatedSlots, debug);
}

@SuppressWarnings("unchecked")
@Override
public <S> Optional<S> retrieve(SlotRead<S> read) {
if (read.slot().equals(slot)) {
return (Optional<S>) Optional.ofNullable(value);
}

if (read.slot().equals(slot2)) {
return (Optional<S>) Optional.ofNullable(value2);
}
return Optional.empty();
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Context2)) {
return false;
}
Context2 context2 = (Context2) o;
return slot.equals(context2.slot) && Objects.equals(value, context2.value)
&& slot2.equals(context2.slot2) && Objects.equals(value2, context2.value2);
}

@Override
public int hashCode() {
return Objects.hash(slot, value, slot2, value2);
}
}
35 changes: 35 additions & 0 deletions pitest-entry/src/main/java/org/pitest/sequence/EmptyContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.pitest.sequence;

import java.util.Optional;

/**
* Specialisation of context with no data
*/
enum EmptyContext implements Context {

WITHOUT_DEBUG(false),
WITH_DEBUG(true);

private final boolean debug;

EmptyContext(boolean debug) {
this.debug = debug;
}

@Override
public boolean debug() {
return debug;
}

@Override
public <S> Context store(SlotWrite<S> slot, S value) {
return new Context1(slot.slot(), value, debug);
}

@SuppressWarnings("unchecked")
@Override
public <S> Optional<S> retrieve(SlotRead<S> slot) {
return Optional.empty();
}

}
55 changes: 55 additions & 0 deletions pitest-entry/src/main/java/org/pitest/sequence/MultiContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.pitest.sequence;

import java.util.IdentityHashMap;

import java.util.Map;
import java.util.Optional;

/**
* Specialisation of context for unlimited values
*/
final class MultiContext implements Context {

private final boolean debug;
private final Map<Slot,Object> slots;

MultiContext(Map<Slot,Object> slots, boolean debug) {
this.slots = slots;
this.debug = debug;
}

@Override
public boolean debug() {
return debug;
}

@Override
public <S> Context store(SlotWrite<S> slot, S value) {
Map<Slot,Object> mutatedSlots = new IdentityHashMap<>(slots);
mutatedSlots.put(slot.slot(), value);
return new MultiContext(mutatedSlots, debug);
}

@SuppressWarnings("unchecked")
@Override
public <S> Optional<S> retrieve(SlotRead<S> slot) {
return Optional.ofNullable((S)slots.get(slot.slot()));
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof MultiContext)) {
return false;
}
MultiContext context = (MultiContext) o;
return slots.equals(context.slots);
}

@Override
public int hashCode() {
return slots.hashCode();
}
}
15 changes: 15 additions & 0 deletions pitest-entry/src/test/java/org/pitest/sequence/Context1Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.pitest.sequence;

import nl.jqno.equalsverifier.EqualsVerifier;
import org.junit.Test;

public class Context1Test {

@Test
public void obeysHashCodeEqualsContract() {
EqualsVerifier.forClass(Context1.class)
.withNonnullFields("slot")
.withIgnoredFields("debug")
.verify();
}
}
16 changes: 16 additions & 0 deletions pitest-entry/src/test/java/org/pitest/sequence/Context2Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.pitest.sequence;

import nl.jqno.equalsverifier.EqualsVerifier;
import org.junit.Test;

public class Context2Test {

@Test
public void obeysHashCodeEqualsContract() {
EqualsVerifier.forClass(Context2.class)
.withNonnullFields("slot")
.withNonnullFields("slot2")
.withIgnoredFields("debug")
.verify();
}
}
Loading

0 comments on commit b209fc0

Please sign in to comment.