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 or 1 values.
  • Loading branch information
Henry Coles committed May 18, 2022
1 parent fbc3215 commit dfc8d5d
Show file tree
Hide file tree
Showing 8 changed files with 262 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 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);
}
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);
}
}
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();
}
}
72 changes: 66 additions & 6 deletions pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,76 @@
package org.pitest.sequence;

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

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;

public class ContextTest {

@Test
public void obeysHashCodeEqualsContract() {
EqualsVerifier.forClass(Context.class)
.withNonnullFields("slots")
.withIgnoredFields("debug")
.verify();
public void retrieveIsStartsEmpty() {
SlotRead<Integer> slot = Slot.create(Integer.class).read();
assertThat(Context.start().retrieve(slot)).isEmpty();
}

@Test
public void canStoreThenRetrieve() {
SlotRead<Integer> slot = Slot.create(Integer.class).read();
Context underTest = Context.start().store(slot.slot().write(), 42);

Optional<Integer> actual = underTest.retrieve(slot);
assertThat(actual).contains(42);
}

@Test
public void canStoreAndRetrieveTwoValues() {
Slot<Integer> slot1 = Slot.create(Integer.class);
Slot<Integer> slot2 = Slot.create(Integer.class);
Context underTest = Context.start()
.store(slot1.write(), 42)
.store(slot2.write(), 101);

assertThat(underTest.retrieve(slot1.read())).contains(42);
assertThat(underTest.retrieve(slot2.read())).contains(101);
}

@Test
public void canStoreAndRetrieveThreeValues() {
Slot<Integer> slot1 = Slot.create(Integer.class);
Slot<Integer> slot2 = Slot.create(Integer.class);
Slot<Integer> slot3 = Slot.create(Integer.class);
Context underTest = Context.start()
.store(slot1.write(), 42)
.store(slot2.write(), 101)
.store(slot3.write(), 8);

assertThat(underTest.retrieve(slot1.read())).contains(42);
assertThat(underTest.retrieve(slot2.read())).contains(101);
assertThat(underTest.retrieve(slot3.read())).contains(8);
}

@Test
public void canStoreAndRetrieveMultipleValues() {
Slot<Integer> slot1 = Slot.create(Integer.class);
Slot<Integer> slot2 = Slot.create(Integer.class);
Slot<Integer> slot3 = Slot.create(Integer.class);
Slot<Integer> slot4 = Slot.create(Integer.class);
Slot<Integer> slot5 = Slot.create(Integer.class);
Context underTest = Context.start()
.store(slot1.write(), 1)
.store(slot2.write(), 2)
.store(slot3.write(), 3)
.store(slot4.write(), 4)
.store(slot5.write(), 5);


assertThat(underTest.retrieve(slot1.read())).contains(1);
assertThat(underTest.retrieve(slot2.read())).contains(2);
assertThat(underTest.retrieve(slot3.read())).contains(3);
assertThat(underTest.retrieve(slot4.read())).contains(4);
assertThat(underTest.retrieve(slot5.read())).contains(5);
}


}
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 MultiContextTest {

@Test
public void obeysHashCodeEqualsContract() {
EqualsVerifier.forClass(MultiContext.class)
.withNonnullFields("slots")
.withIgnoredFields("debug")
.verify();
}

}

0 comments on commit dfc8d5d

Please sign in to comment.