blob: f85d4bea7d71e91d755da44487132596a648b2a2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017-2020 Dortmund University of Applied Sciences and Arts and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Dortmund University of Applied Sciences and Arts - initial API and implementation
*******************************************************************************/
package org.eclipse.app4mc.multicore.partitioning.utils;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.app4mc.amalthea.model.AccessPrecedenceSpec;
import org.eclipse.app4mc.amalthea.model.AccessPrecedenceType;
import org.eclipse.app4mc.amalthea.model.AmaltheaFactory;
import org.eclipse.app4mc.amalthea.model.ConstraintsModel;
import org.eclipse.app4mc.amalthea.model.ProcessPrototype;
import org.eclipse.app4mc.amalthea.model.Runnable;
import org.eclipse.app4mc.amalthea.model.RunnableCall;
import org.eclipse.app4mc.amalthea.model.RunnableSequencingConstraint;
import org.eclipse.app4mc.amalthea.model.SWModel;
import org.eclipse.app4mc.multicore.partitioning.IParConstants;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.jface.preference.IPreferenceStore;
import org.jgrapht.DirectedGraph;
import org.jgrapht.alg.CycleDetector;
import org.jgrapht.alg.cycle.TarjanSimpleCycles;
import org.jgrapht.graph.DefaultDirectedWeightedGraph;
import org.jgrapht.graph.DirectedSubgraph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class taskes advantage of the JGrahT library and is able to remove
* cycles of a directed graph. Cycles can be removed by minimal decompositions
* and in a way, that, with respect to the whole graph, the resulting graph
* provides a great treespread e.i. a graph structure with better parallelism
* exploitation possibility
*/
public class CycleElimination {
private final SWModel swm;
private final ConstraintsModel cm;
private boolean minimialEdgeElim = false;
private boolean efficientEdgeInCycle = false;
private static final Logger LOGGER = LoggerFactory.getLogger(CycleElimination.class);
/**
* constructor
*
* @param swm
* @param cm
*/
public CycleElimination(final SWModel swm, final ConstraintsModel cm) {
this.swm = swm;
this.cm = cm;
}
public SWModel getSwm() {
return this.swm;
}
public ConstraintsModel getCm() {
return this.cm;
}
/**
* simply checks, if there exist cycles or a cycle within the SWModel and
* Constrainsmodel (graph)
*/
public boolean containsCycles() {
final DirectedGraph<Runnable, RunnableSequencingConstraint> graph = createJGraphT();
final CycleDetector<Runnable, RunnableSequencingConstraint> cd = new CycleDetector<>(graph);
return cd.detectCycles();
}
/**
* Creates Vertexes foreach Runnable in the software model and weighted
* edges for each RunnableSequenceConstraint in the ConstraintsModel,
*
* @return a directed graph with weighted edges
*/
public DirectedGraph<Runnable, RunnableSequencingConstraint> createJGraphT() {
if (this.swm.getRunnables() == null || this.cm.getRunnableSequencingConstraints() == null) {
return null;
}
final DefaultDirectedWeightedGraph<Runnable, RunnableSequencingConstraint> test = new DefaultDirectedWeightedGraph<>(
RunnableSequencingConstraint.class);
for (final Runnable r : this.swm.getRunnables()) {
test.addVertex(r);
}
for (final RunnableSequencingConstraint rsc : this.cm.getRunnableSequencingConstraints()) {
if (rsc.getRunnableGroups().get(0).getRunnables().get(0) != null
&& rsc.getRunnableGroups().get(1).getRunnables().get(0) != null) {
if (!test.vertexSet().contains(rsc.getRunnableGroups().get(0).getRunnables().get(0))
|| !test.vertexSet().contains(rsc.getRunnableGroups().get(1).getRunnables().get(0))) {
LOGGER.error("Runnables of RSC {} are not contained in JGraph", rsc.getName());
}
else {
test.addEdge(rsc.getRunnableGroups().get(0).getRunnables().get(0), rsc.getRunnableGroups().get(1).getRunnables().get(0),
rsc);
}
}
}
return test;
}
public void setparams(final boolean efficientEdgeInCycle, final boolean minimalEdgeDis) {
setEfficientEdgeInCycle(efficientEdgeInCycle);
setMinimialEdgeElim(minimalEdgeDis);
}
public void setparams(final IPreferenceStore s) {
setEfficientEdgeInCycle(s.getBoolean(IParConstants.PRE_EFF_EDGE));
setMinimialEdgeElim(s.getBoolean(IParConstants.PRE_MIN_EDGES));
}
/**
* Converts given scycles into list of directed subgraphs of Runnables and
* RunnableSequencingConstraints
*
* @param scycles
* A given List (simple cycles) of a list of runnables (derived
* from jgrapht tarjan algorithm)
* @param graph
* A directed subgraph required for edge derivation
* @return List<DirectedSubGraph<Runnable, RunnablSequencingConstraint>>
* List of cycles
*/
private List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> convertSimpleCycleToSubGraph(final List<List<Runnable>> scycles,
final DirectedGraph<Runnable, RunnableSequencingConstraint> graph) {
final List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> cycles = new BasicEList<>();
for (final List<Runnable> cycle : scycles) {
final Set<RunnableSequencingConstraint> edgeSubset = new HashSet<>();
// add edges along with given runnables
for (int i = 0; i < cycle.size(); i++) {
if (i == cycle.size() - 1) {
// connect last runnable with first runnable
edgeSubset.add(graph.getEdge(cycle.get(i), cycle.get(0)));
}
else {
edgeSubset.add(graph.getEdge(cycle.get(i), cycle.get(i + 1)));
}
}
final Set<Runnable> vertexSet = new HashSet<>();
for (final Runnable r : cycle) {
vertexSet.add(r);
}
final DirectedSubgraph<Runnable, RunnableSequencingConstraint> dsg = new DirectedSubgraph<>(graph, vertexSet, edgeSubset);
cycles.add(dsg);
}
return cycles;
}
/**
* removes given RSC from constraints models, adds a corresponding
* accessPrecedence, calculates new cycle list
*
* @param graph
* required for new cycle calculation
* @param cycles
* @param rsc
* @return
*/
private List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> removeRSC(
final DirectedGraph<Runnable, RunnableSequencingConstraint> graph, final RunnableSequencingConstraint rsc) {
LOGGER.debug("RSC to be converted to AccessPrecedence: {}-->{}", rsc.getRunnableGroups().get(0).getRunnables().get(0).getName(),
rsc.getRunnableGroups().get(1).getRunnables().get(0).getName());
convertRSCToAP(rsc);
graph.removeEdge(rsc);
final TarjanSimpleCycles<Runnable, RunnableSequencingConstraint> tsc = new TarjanSimpleCycles<>(graph);
final List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> cyclesl = convertSimpleCycleToSubGraph(tsc.findSimpleCycles(),
graph);
if (cyclesl.isEmpty()) {
return Collections.emptyList();
}
final List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> deleteMe = new BasicEList<>();
for (final DirectedSubgraph<Runnable, RunnableSequencingConstraint> cycle : cyclesl) {
if (cycle.vertexSet().size() == 1 || cycle.vertexSet().isEmpty()) {
deleteMe.add(cycle);
}
}
for (final DirectedSubgraph<Runnable, RunnableSequencingConstraint> cycle : deleteMe) {
cyclesl.remove(cycle);
}
return cyclesl;
}
/**
*
* deletes the given @param rsc from the swmodel and creates an access
* precedence correspondingly
*/
private void convertRSCToAP(final RunnableSequencingConstraint rsc) {
final AmaltheaFactory swf = AmaltheaFactory.eINSTANCE;
final AccessPrecedenceSpec ap = swf.createAccessPrecedenceSpec();
ap.setLabel(new Helper().getCommonLabel(rsc.getRunnableGroups().get(0).getRunnables().get(0),
rsc.getRunnableGroups().get(1).getRunnables().get(0)));
ap.setOrigin(rsc.getRunnableGroups().get(0).getRunnables().get(0));
ap.setTarget(rsc.getRunnableGroups().get(1).getRunnables().get(0));
ap.setOrderType(AccessPrecedenceType.IGNORE_WR);
if (!this.swm.getProcessPrototypes().isEmpty()) {
final Set<AccessPrecedenceSpec> aps = new HashSet<>();
aps.add(ap);
new Helper().assignAPs(aps);
}
else {
LOGGER.debug("No ProcessPrototype for cycle dissolution - creating ProcessPrototype with all runnables");
final ProcessPrototype pp = swf.createProcessPrototype();
pp.setName("allRunnables");
for (final Runnable r : this.swm.getRunnables()) {
final RunnableCall trc = swf.createRunnableCall();
trc.setRunnable(r);
pp.getRunnableCalls().add(trc);
}
pp.getAccessPrecedenceSpec().add(ap);
this.swm.getProcessPrototypes().add(pp);
}
this.cm.getRunnableSequencingConstraints().remove(rsc);
}
/**
* Due to cycles must be avoided within DAGs, this method analyzes all
* cycles within @param cycles, identifies the most cost intensive cycle and
* returns an edge within this cycle, that cuts the cycle either into two
* approximately equally branches, into a preceding branch or into a
* successing branch
*
* @param cycles
* the cycles within the graph
* @return edge, that shall be removed
*/
private RunnableSequencingConstraint getMostEfficientRemoveEdge(
final List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> cycles) {
final DirectedSubgraph<Runnable, RunnableSequencingConstraint> bc = getBiggestCycle(cycles);
Runnable source = null;
if (null != bc) {
source = getBiggestPrecedingRunnable(bc);
}
else {
return null;
}
long sourcert = new Helper().getPreceedingRunTimeCycle(this.cm, source);
Runnable source2 = getBiggestSuccessingRunnable(bc);
final long source2rt = new Helper().getSucceedingRunTimeCycle(this.cm, source2);
boolean preceding = true;
if (source2rt > sourcert) {
preceding = false;
source = source2;
sourcert = source2rt;
}
else {
source2 = source;
}
long cycleRestWeight = 0;
for (final Runnable rt : bc.vertexSet()) {
if (rt != source) {
cycleRestWeight += new Helper().getInstructions(rt);
}
}
RunnableSequencingConstraint removeMe = this.cm.getRunnableSequencingConstraints().get(0);
if (cycleRestWeight < (sourcert - new Helper().getInstructions(source))) {
removeMe = selEdgFrSmCyclRstW(bc, source, source2, preceding, removeMe);
}
else {
removeMe = selEdgFrBggrCycleRstW(bc, source, cycleRestWeight);
}
return removeMe;
}
private RunnableSequencingConstraint selEdgFrBggrCycleRstW(final DirectedSubgraph<Runnable, RunnableSequencingConstraint> bc,
Runnable source, long cycleRestWeight) {
RunnableSequencingConstraint removeMe;
cycleRestWeight /= 2;
LOGGER.debug("Cycle branch length: {}", cycleRestWeight);
long branchLength = 0;
removeMe = (RunnableSequencingConstraint) bc.outgoingEdgesOf(source).toArray()[0];
while (branchLength < cycleRestWeight) {
final Runnable target = bc.getEdgeTarget((RunnableSequencingConstraint) bc.outgoingEdgesOf(source).toArray()[0]);
final long branchLengthA = branchLength + new Helper().getInstructions(target);
if (branchLengthA == cycleRestWeight) {
removeMe = (RunnableSequencingConstraint) bc.outgoingEdgesOf(target).toArray()[0];
break;
}
else if (branchLengthA < cycleRestWeight) {
branchLength = branchLengthA;
source = target;
}
else {
final long distCycleRestWeightb = cycleRestWeight - branchLength;
final long distCycleRestWeighta = branchLengthA - cycleRestWeight;
if (distCycleRestWeighta > distCycleRestWeightb) {
return (RunnableSequencingConstraint) bc.outgoingEdgesOf(source).toArray()[0];
}
return (RunnableSequencingConstraint) bc.outgoingEdgesOf(target).toArray()[0];
}
}
return removeMe;
}
private RunnableSequencingConstraint selEdgFrSmCyclRstW(final DirectedSubgraph<Runnable, RunnableSequencingConstraint> bc,
Runnable source, final Runnable source2, final boolean preceding, RunnableSequencingConstraint removeMe) {
if (preceding) {
while (bc.incomingEdgesOf(source).size() == 1 && ((RunnableSequencingConstraint) bc.incomingEdgesOf(source).toArray()[0])
.getRunnableGroups().get(0).getRunnables().get(0) != source2) {
removeMe = (RunnableSequencingConstraint) bc.incomingEdgesOf(source).toArray()[0];
source = removeMe.getRunnableGroups().get(0).getRunnables().get(0);
}
}
else {
while (bc.outgoingEdgesOf(source).size() == 1 && ((RunnableSequencingConstraint) bc.outgoingEdgesOf(source).toArray()[0])
.getRunnableGroups().get(1).getRunnables().get(0) != source2) {
removeMe = (RunnableSequencingConstraint) bc.outgoingEdgesOf(source).toArray()[0];
source = removeMe.getRunnableGroups().get(1).getRunnables().get(0);
}
}
return removeMe;
}
/**
* identifies a cycle from the given list of cycles, thats sum of included
* runnable's instructions if the largest
*
* @param cycles
* @return cycle with most instructions (derived from included runnables)
*/
private DirectedSubgraph<Runnable, RunnableSequencingConstraint> getBiggestCycle(
final List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> cycles) {
// bcw= biggest cycle weight
long bcw = 0;
DirectedSubgraph<Runnable, RunnableSequencingConstraint> bc = null;
for (final DirectedSubgraph<Runnable, RunnableSequencingConstraint> dsg : cycles) {
// cw=current weight
final long cw = getCycleWeight(dsg);
if (cw > bcw) {
bcw = cw;
bc = dsg;
}
else if (cw == bcw) {
// two cycles with same weight found
final long icw1 = getBiggestPrecedingRuntime(bc);
final long icw2 = getBiggestPrecedingRuntime(dsg);
if (icw2 > icw1) {
bcw = cw;
bc = dsg;
}
}
}
return bc;
}
/**
* @return (long) biggest preceding sum of instructions of the @param cycle
* runnables w.r.t. total graph
*/
private long getBiggestPrecedingRuntime(final DirectedSubgraph<Runnable, RunnableSequencingConstraint> bc) {
long bpr = 0;
if (null != bc) {
for (final Runnable r : bc.vertexSet()) {
final long pr = new Helper().getPreceedingRunTimeCycle(this.cm, r);
if (pr > bpr) {
bpr = pr;
}
}
}
return bpr;
}
/**
* @return runnable, thats preceding sum of instructions of the @param cycle
* runnables w.r.t. total graph is the biggest
*/
private Runnable getBiggestPrecedingRunnable(final DirectedSubgraph<Runnable, RunnableSequencingConstraint> bc) {
long bpr = 0;
Runnable rr = null;
for (final Runnable r : bc.vertexSet()) {
final long pr = new Helper().getPreceedingRunTimeCycle(this.cm, r);
if (pr > bpr) {
bpr = pr;
rr = r;
}
}
return rr;
}
/**
* @return runnable, thats suceeding sum of instructions of the @param cycle
* runnables w.r.t. total graph is the biggest
*/
private Runnable getBiggestSuccessingRunnable(final DirectedSubgraph<Runnable, RunnableSequencingConstraint> bc) {
long bpr = 0;
Runnable rr = null;
for (final Runnable r : bc.vertexSet()) {
final long pr = new Helper().getSucceedingRunTimeCycle(this.cm, r);
if (pr > bpr) {
bpr = pr;
rr = r;
}
}
return rr;
}
/**
* calculates a list of edges, used by maximal number of cycles. E.g. 2
* Edges E1, E2 used within 3 Cycles, Edge E3 used in 2 Cycles, just edges
* E1 and E2 are returned.
*
* @param cycles
* @return
*/
private RunnableSequencingConstraint getMostCommonEdges(final List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> cycles) {
if (cycles.size() == 1) {
return getMostEfficientRemoveEdge(cycles);
}
final Map<RunnableSequencingConstraint, Integer> mceReps = new HashMap<>();
for (final RunnableSequencingConstraint rsc : this.cm.getRunnableSequencingConstraints()) {
mceReps.put(rsc, 0);
}
for (final DirectedSubgraph<Runnable, RunnableSequencingConstraint> dsg : cycles) {
for (final RunnableSequencingConstraint rsc : dsg.edgeSet()) {
mceReps.put(rsc, mceReps.get(rsc) + 1);
}
}
int reps = 0;
RunnableSequencingConstraint mce = null;
final Iterator<Entry<RunnableSequencingConstraint, Integer>> it = mceReps.entrySet().iterator();
while (it.hasNext()) {
final Map.Entry<RunnableSequencingConstraint, Integer> pair = it.next();
if (pair.getValue() > reps) {
reps = pair.getValue();
mce = pair.getKey();
}
it.remove();
}
if (reps == 1) {
return null;
}
return mce;
}
/**
* @param dsg
* = DirectedSubGraph, that forms the cycle
* @return (long) sum of the cycle's runnable's instructions
*/
private long getCycleWeight(final DirectedSubgraph<Runnable, RunnableSequencingConstraint> dsg) {
long cw = 0;
for (final Runnable r : dsg.vertexSet()) {
cw += new Helper().getInstructions(r);
}
return cw;
}
public void setEfficientEdgeInCycle(final boolean efficientEdgeInCycle) {
this.efficientEdgeInCycle = efficientEdgeInCycle;
}
public void setMinimialEdgeElim(final boolean minimialEdgeElim) {
this.minimialEdgeElim = minimialEdgeElim;
}
public boolean isMinimialEdgeElim() {
return this.minimialEdgeElim;
}
public boolean isEfficientEdgeInCycle() {
return this.efficientEdgeInCycle;
}
/**
* adjusts the class's constraints and swm-model by detecting cycles and
* decomposing them via converting RunnableSequencingConstraints to
* AccessPrecedences. Decomposition is configured by global boolean
* minimalEdgeElim, that defines, if RSCs are preffered, that decompose
* multiple cycles and gloabl boolean efficientEdgeInCycle that defines, if
* simply any edge is decomosed in a cycle or a calculated edge, that
* results in a graph featuring a great parallelization structure
*/
public IStatus run(final IProgressMonitor monitor) {
DirectedGraph<Runnable, RunnableSequencingConstraint> graph;
graph = createJGraphT();
if (graph == null) {
LOGGER.error("No Constraints model!");
return Status.CANCEL_STATUS;
}
if (!this.minimialEdgeElim) {
final Cycle<Runnable, RunnableSequencingConstraint> mc = new Cycle<>(graph);
if (monitor == null) {
mc.findSimpleCycles();
for (final RunnableSequencingConstraint rsc : mc.getRemovedEdges()) {
convertRSCToAP(rsc);
}
}
else {
monitor.beginTask("Cycle Elimination", mc.getRemovedEdges().size());
mc.findSimpleCycles();
for (final RunnableSequencingConstraint rsc : mc.getRemovedEdges()) {
monitor.worked(1);
convertRSCToAP(rsc);
}
monitor.done();
}
return Status.OK_STATUS;
}
/*TiernanSimpleCycles, SzwarcfiterLauerSimpleCycles, or JohnsonSimpleCycles can be used here, but all scale bad with high number of dependencies and cycles */
final TarjanSimpleCycles<Runnable, RunnableSequencingConstraint> tsc = new TarjanSimpleCycles<>();
tsc.setGraph(graph);
final List<List<Runnable>> scycles = tsc.findSimpleCycles();
final List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> cycles = convertSimpleCycleToSubGraph(scycles, graph);
final StringBuilder sb = new StringBuilder();
sb.append("SimpleCycles: ");
for (final List<Runnable> rl : scycles) {
sb.append("\n" + scycles.indexOf(rl) + ":(");
for (final Runnable r : rl) {
sb.append(r.getName() + ", ");
}
sb.append(")");
}
LOGGER.debug("{}", sb);
monitor.beginTask("Cycle elimintation", cycles.size());
LOGGER.debug("Removing {} cycles", cycles.size());
removeCycles(monitor, graph, cycles);
return Status.OK_STATUS;
}
private IStatus removeCycles(final IProgressMonitor monitor, final DirectedGraph<Runnable, RunnableSequencingConstraint> graph,
List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> cycles) {
RunnableSequencingConstraint re;
while (cycles != null && !cycles.isEmpty()) {
monitor.worked(1);
re = idntfyEdge(cycles);
if (re != null) {
cycles = removeRSC(graph, re);
}
else {
LOGGER.error("Cycle elimination could not find remove edge!");
return Status.CANCEL_STATUS;
}
}
return Status.OK_STATUS;
}
private RunnableSequencingConstraint idntfyEdge(final List<DirectedSubgraph<Runnable, RunnableSequencingConstraint>> cycles) {
RunnableSequencingConstraint re;
if (this.minimialEdgeElim) {
re = getMostCommonEdges(cycles);
if (re == null) {
// no common edges among cycles
if (this.efficientEdgeInCycle) {
re = getMostEfficientRemoveEdge(cycles);
}
else {
re = cycles.get(0).edgeSet().iterator().next();
}
}
}
else {
// no minimal edge dissolution
if (this.efficientEdgeInCycle) {
re = getMostEfficientRemoveEdge(cycles);
}
else {
re = cycles.get(0).edgeSet().iterator().next();
}
}
return re;
}
}