Skip to content

Commit

Permalink
Add reordering optimizations, fix #35
Browse files Browse the repository at this point in the history
  • Loading branch information
julgus committed Jun 29, 2023
1 parent 1e53129 commit 8923a66
Show file tree
Hide file tree
Showing 8 changed files with 806 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@
<artifactId>pipeline-standard</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.speedment.jpastreamer</groupId>
<artifactId>field</artifactId>
</dependency>
<dependency>
<groupId>com.speedment.jpastreamer</groupId>
<artifactId>field</artifactId>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@

import com.speedment.jpastreamer.interopoptimizer.IntermediateOperationOptimizer;
import com.speedment.jpastreamer.interopoptimizer.IntermediateOperationOptimizerFactory;
import com.speedment.jpastreamer.interopoptimizer.standard.internal.strategy.RemoveOrderAffectingOperations;
import com.speedment.jpastreamer.interopoptimizer.standard.internal.strategy.SquashDistinct;
import com.speedment.jpastreamer.interopoptimizer.standard.internal.strategy.SquashFilter;
import com.speedment.jpastreamer.interopoptimizer.standard.internal.strategy.SquashLimit;
import com.speedment.jpastreamer.interopoptimizer.standard.internal.strategy.SquashSkip;
import com.speedment.jpastreamer.interopoptimizer.standard.internal.strategy.SquashSorted;
import com.speedment.jpastreamer.interopoptimizer.standard.internal.strategy.*;
import com.speedment.jpastreamer.pipeline.intermediate.IntermediateOperationFactory;
import com.speedment.jpastreamer.rootfactory.RootFactory;

Expand Down Expand Up @@ -53,6 +48,7 @@ public InternalIntermediateOperationOptimizerFactory() {
registerOptimizer(new SquashFilter<>(intermediateOperationFactory));
registerOptimizer(new SquashSorted<>(intermediateOperationFactory));
registerOptimizer(new SquashDistinct(intermediateOperationFactory));
registerOptimizer(new MoveAnonymousLambdaOperations(), Priority.LOWEST);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.speedment.jpastreamer.interopoptimizer.standard.internal.strategy;

import com.speedment.jpastreamer.field.Field;
import com.speedment.jpastreamer.field.predicate.SpeedmentPredicate;
import com.speedment.jpastreamer.interopoptimizer.IntermediateOperationOptimizer;
import com.speedment.jpastreamer.pipeline.Pipeline;
import com.speedment.jpastreamer.pipeline.intermediate.IntermediateOperation;
import com.speedment.jpastreamer.pipeline.intermediate.IntermediateOperationType;

import java.util.LinkedList;

import static com.speedment.jpastreamer.pipeline.intermediate.IntermediateOperationType.*;

public class MoveAnonymousLambdaOperations implements IntermediateOperationOptimizer {

@Override
public <T> Pipeline<T> optimize(Pipeline<T> pipeline) {

LinkedList<IntermediateOperation<?, ?>> intermediateOperations = pipeline.intermediateOperations();
LinkedList<IntermediateOperation<?, ?>> optimizedOperations = new LinkedList<>();
for (int i = 0; i < intermediateOperations.size(); i++) {
final IntermediateOperation<?, ?> intermediateOperation = intermediateOperations.get(i);
if (i == 0 || !movable(intermediateOperation.type())) {
optimizedOperations.add(intermediateOperation);
continue;
}
int position = optimizedOperations.size(); // default - end of list
for(int j = optimizedOperations.size() - 1; j >= 0; j--) {
final IntermediateOperation<?, ?> currentOperation = optimizedOperations.get((j));
if(swappable(intermediateOperation.type(), currentOperation.type()) && anonymousLambda(currentOperation)) {
position = j;
} else {
break;
}
}
if (position < optimizedOperations.size()) {
optimizedOperations.add(position, intermediateOperation);
} else {
optimizedOperations.add(intermediateOperation);
}
}

// Update pipeline
for (int k = 0; k < intermediateOperations.size(); k++) {
final IntermediateOperation<?, ?> intermediateOperation = optimizedOperations.get(k);
intermediateOperations.set(k, intermediateOperation);
}

return pipeline;
}

public <T> Pipeline<T> optimize2(Pipeline<T> pipeline) {

LinkedList<IntermediateOperation<?, ?>> intermediateOperations = pipeline.intermediateOperations();

int i = 0;
while(i < intermediateOperations.size() - 1) {
final IntermediateOperation<?, ?> intermediateOperation = intermediateOperations.get(i);
final IntermediateOperationType iot = intermediateOperation.type();
if (movable(iot) && !anonymousLambda(intermediateOperation) ) {
// We only move movable operations with anonymous lambdas
int j = i + 1;
int currentPos = i;
while (j < intermediateOperations.size()) {
final IntermediateOperation<?, ?> next = intermediateOperations.get(j);
final IntermediateOperationType iotNext = next.type();
if (swappable(iot, iotNext)) { // check if current lambda can be swapped with next operation
if (iotNext == DISTINCT || anonymousLambda(next)) {
i = -1;
currentPos = swapOperations(intermediateOperations, currentPos, j);
} else {
j++;
i++;
continue;
}
} else {
break;
}
j++;
}
}
i++;
}

return pipeline;
}


private boolean movable(final IntermediateOperationType type) {
return type == FILTER || type == SORTED || type == DISTINCT;
}

private boolean swappable(final IntermediateOperationType type, final IntermediateOperationType nextType) {
// Anonymous sorts and lambdas can be swapped with a distinct operator to allow the inclusion
// of the distinct operation in the query.
if (type == FILTER) {
return nextType == FILTER || nextType == SORTED || nextType == DISTINCT;
} else if (type == SORTED) {
return nextType == FILTER || nextType == DISTINCT;
} else if (type == DISTINCT) {
return nextType == FILTER || nextType == SORTED;
}
return false;
}

private int swapOperations(final LinkedList<IntermediateOperation<?, ?>> intermediateOperations,
final int index1, final int index2)
{
if (0 <= index1 && index1 < intermediateOperations.size() && 0 <= index2 && index2 < intermediateOperations.size()) {
final IntermediateOperation<?, ?> io1 = intermediateOperations.get(index1);
final IntermediateOperation<?, ?> io2 = intermediateOperations.get(index2);

intermediateOperations.set(index1, io2);
intermediateOperations.set(index2, io1);

return index2;
}
return -1;
}

private boolean anonymousLambda(final IntermediateOperation<?, ?> operation) {
if (operation.type() == DISTINCT){
return true;
}
final Object[] arguments = operation.arguments();
return !(arguments != null && (arguments[0] instanceof SpeedmentPredicate || arguments[0] instanceof Field));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
module jpastreamer.interopoptimizer.standard {
requires transitive jpastreamer.interopoptimizer;
requires jpastreamer.rootfactory;
requires jpastreamer.field;

exports com.speedment.jpastreamer.interopoptimizer.standard;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.speedment.jpastreamer.interopoptimizer.standard.internal.strategy;

import com.speedment.jpastreamer.pipeline.Pipeline;
import com.speedment.jpastreamer.pipeline.PipelineFactory;
import com.speedment.jpastreamer.pipeline.intermediate.IntermediateOperation;
import com.speedment.jpastreamer.pipeline.intermediate.IntermediateOperationFactory;
import com.speedment.jpastreamer.rootfactory.RootFactory;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

import java.lang.reflect.Field;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

abstract class InternalOptimizerTest<ENTITY> {

protected final PipelineFactory pipelineFactory = RootFactory.getOrThrow(PipelineFactory.class, ServiceLoader::load);
protected final IntermediateOperationFactory iof = RootFactory.getOrThrow(IntermediateOperationFactory.class, ServiceLoader::load);
protected final MoveAnonymousLambdaOperations optimizer = new MoveAnonymousLambdaOperations();

abstract Class<ENTITY> getEntityClass();

@TestFactory
Stream<DynamicTest> modify() {
return pipelines().map(testCase -> dynamicTest(testCase.getName(), () -> {
this.optimizer.optimize(testCase.getPipeline());

assertTestCase(testCase);
}));
}

protected abstract Stream<PipelineTestCase<ENTITY>> pipelines();

protected Pipeline<ENTITY> createPipeline(final IntermediateOperation<?, ?>... operations) {
final Pipeline<ENTITY> pipeline = pipelineFactory.createPipeline(getEntityClass());

for (IntermediateOperation<?, ?> operation : operations) {
pipeline.intermediateOperations().add(operation);
}

return pipeline;
}

private void assertTestCase(final PipelineTestCase<ENTITY> testCase) {
final Pipeline<ENTITY> unoptimized = testCase.getPipeline();
final Pipeline<ENTITY> optimized = testCase.getExpectedPipeline();

final List<IntermediateOperation<?, ?>> unoptimizedIntermediateOperations = unoptimized.intermediateOperations();
final List<IntermediateOperation<?, ?>> optimizedIntermediateOperations = optimized.intermediateOperations();

assertEquals(optimizedIntermediateOperations.size(), unoptimizedIntermediateOperations.size());

for (int i = 0; i < unoptimizedIntermediateOperations.size(); i++) {
final IntermediateOperation<?, ?> unoptimizedOperation = unoptimizedIntermediateOperations.get(i);
final IntermediateOperation<?, ?> optimizedOperation = optimizedIntermediateOperations.get(i);

assertEquals(optimizedOperation.type(), unoptimizedOperation.type());
assertArguments(optimizedOperation.arguments(), unoptimizedOperation.arguments());
}
}

protected void assertArguments(final Object[] expected, final Object[] actual) {
if (expected != actual) {
assertNotNull(expected);
assertNotNull(actual);
assertEquals(expected.length, actual.length);

for(int i = 0; i < expected.length; ++i) {
Object expectedElement = expected[i];
Object actualElement = actual[i];
if (expectedElement != actualElement) {
final Field[] actualDeclaredFields = actualElement.getClass().getDeclaredFields();
final Field[] expectedDeclaredFields = expectedElement.getClass().getDeclaredFields();
for (int j = 0; j < expectedDeclaredFields.length; ++j) {
assertEquals(actualDeclaredFields[i], expectedDeclaredFields[i]);
}
}
}
}
}

protected static final class PipelineTestCase<ENTITY> {

private final String name;
private final Pipeline<ENTITY> pipeline;
private final Pipeline<ENTITY> expectedPipeline;

protected PipelineTestCase(
final String name,
final Pipeline<ENTITY> pipeline,
final Pipeline<ENTITY> expectedPipeline
) {
this.name = name;
this.pipeline = pipeline;
this.expectedPipeline = expectedPipeline;
}

public String getName() {
return name;
}

public Pipeline<ENTITY> getPipeline() {
return pipeline;
}

public Pipeline<ENTITY> getExpectedPipeline() {
return expectedPipeline;
}
}


}
Loading

0 comments on commit 8923a66

Please sign in to comment.