Skip to content

Commit 5d282fa

Browse files
author
Dominik Helm
committed
Factor out common code
1 parent f078658 commit 5d282fa

File tree

6 files changed

+260
-638
lines changed

6 files changed

+260
-638
lines changed

OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package org.opalj
33
package fpcf
44

55
import org.opalj.fpcf.AnalysisScenario.AnalysisAutoConfigKey
6-
import org.opalj.fpcf.AnalysisScenario.AnalysisScheduleLazyTransformerInMultipleBatches
76
import org.opalj.fpcf.AnalysisScenario.AnalysisScheduleStrategy
87
import org.opalj.fpcf.scheduling.IndependentPhaseMergeScheduling
98
import org.opalj.fpcf.scheduling.MaximumPhaseScheduling
@@ -12,6 +11,7 @@ import org.opalj.fpcf.scheduling.SmallestPhaseMergeScheduling
1211
import org.opalj.graphs.Graph
1312
import org.opalj.log.LogContext
1413
import org.opalj.log.OPALLogger
14+
import org.opalj.si.Project
1515
import org.opalj.util.PerformanceEvaluation.time
1616

1717
/**
@@ -260,7 +260,9 @@ class AnalysisScenario[A](val ps: PropertyStore) {
260260
}
261261
}
262262

263-
val analysisAutoConfig = BaseConfig.getBoolean(AnalysisAutoConfigKey)
263+
implicit val config = propertyStore.context(classOf[Project]).config
264+
265+
val analysisAutoConfig = config.getBoolean(AnalysisAutoConfigKey)
264266
val underivedProperties = usedProperties -- derivedProperties
265267
underivedProperties
266268
.filterNot { underivedProperty => alreadyComputedPropertyKinds.contains(underivedProperty.pk.id) }
@@ -287,42 +289,37 @@ class AnalysisScenario[A](val ps: PropertyStore) {
287289

288290
// This code implements different scheduling strategies for batching computations based on their dependencies and properties.
289291

290-
val scheduleStrategy = BaseConfig.getAnyRef(AnalysisScheduleStrategy)
291-
val scheduleLazyTransformerInAllBatches =
292-
BaseConfig.getBoolean(AnalysisScheduleLazyTransformerInMultipleBatches)
292+
val scheduleStrategy = config.getAnyRef(AnalysisScheduleStrategy)
293293

294294
// The match statement handles three main scheduling strategies: "SPS", "MPS", "IPMS", and "SPMS".
295295
// Each strategy defines how computations are scheduled into batches based on their dependencies and other constraints.
296296
scheduleStrategy match {
297297
case SinglePhaseScheduling.name =>
298298
// SPS (Single Phase Scheduling) schedules all computations in a single batch.
299-
val spsStrategy = new SinglePhaseScheduling[A](ps)
299+
val spsStrategy = SinglePhaseScheduling
300300
this.scheduleBatches = spsStrategy.schedule(ps, allCS)
301301

302302
case MaximumPhaseScheduling.name =>
303303
// MPS (Maximum Phase Scheduling) breaks down computations into multiple phases based on dependencies and computation types.
304-
val mpsStrategy = new MaximumPhaseScheduling[A](ps, scheduleLazyTransformerInAllBatches)
304+
val mpsStrategy = MaximumPhaseScheduling
305305
this.scheduleBatches = mpsStrategy.schedule(ps, allCS)
306306

307307
case IndependentPhaseMergeScheduling.name =>
308308
// IPMS (Independent Phase Merge Scheduling) extends MPS by merging independent batches to optimize parallelism.
309-
val ipmsStrategy = new IndependentPhaseMergeScheduling[A](ps, scheduleLazyTransformerInAllBatches)
309+
val ipmsStrategy = IndependentPhaseMergeScheduling
310310
this.scheduleBatches = ipmsStrategy.schedule(ps, allCS)
311311

312312
case SmallestPhaseMergeScheduling.name =>
313313
// SPMS (Smallest Phase Merge Scheduling) further optimizes phases merging batches based on the amount of analysis.
314-
val spmsStrategy = new SmallestPhaseMergeScheduling[A](ps, scheduleLazyTransformerInAllBatches)
314+
val spmsStrategy = SmallestPhaseMergeScheduling
315315
this.scheduleBatches = spmsStrategy.schedule(ps, allCS)
316316

317317
case _ => throw new IllegalStateException(s"Invalid scheduler configuration: $scheduleStrategy");
318318
}
319319

320-
OPALLogger.info(
321-
"scheduler",
322-
s"scheduling strategy ${scheduleStrategy} ${if (scheduleLazyTransformerInAllBatches) "with Lazy/Transformer in multiple phases"
323-
else ""} is selected"
324-
)
325-
} { t => OPALLogger.info("scheduler", s"initialization of Scheduler took ${t.toSeconds}") }
320+
OPALLogger.info("scheduler", s"scheduling strategy ${scheduleStrategy} is selected")
321+
322+
} { t => OPALLogger.info("scheduler", s"computation of schedule took ${t.toSeconds}") }
326323

327324
Schedule(
328325
scheduleBatches,

OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/IndependentPhaseMergeScheduling.scala

Lines changed: 55 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -3,266 +3,86 @@ package org.opalj
33
package fpcf
44
package scheduling
55

6-
import org.opalj.collection.IntIterator
7-
import org.opalj.graphs.sccs
8-
import org.opalj.graphs.topologicalSort
9-
106
/**
117
* Independent Phase Merge Scheduling (IPMS) Strategy.
128
* Merges independent batches to optimize parallelism.
139
*/
14-
class IndependentPhaseMergeScheduling[A](ps: PropertyStore, scheduleLazyTransformerInAllBatches: Boolean)
15-
extends SchedulingStrategy[A] {
16-
17-
val name = "IPMS"
18-
19-
override def schedule(
20-
ps: PropertyStore,
21-
allCS: Set[ComputationSpecification[A]]
22-
): List[PhaseConfiguration[A]] = {
23-
24-
// Creates a map from ComputationSpecifications to their indices
25-
val computationSpecificationMap: Map[ComputationSpecification[A], Int] = allCS.zipWithIndex.toMap
26-
val computationSpecificationArray: Array[ComputationSpecification[A]] = allCS.toArray
27-
28-
// Initializes an empty schedule graph
29-
var scheduleGraph: Map[Int, Set[Int]] = Map.empty
30-
31-
// Helper functions and logic for IPMS scheduling follow...
32-
def getAllCSFromPropertyBounds(
33-
properties: Set[PropertyBounds]
34-
): Set[ComputationSpecification[A]] = {
35-
def containsProperty(cs: ComputationSpecification[A], property: PropertyBounds): Boolean =
36-
cs.derivesLazily.contains(property) ||
37-
cs.derivesCollaboratively.contains(property) ||
38-
cs.derivesEagerly.contains(property)
39-
40-
allCS.filter(cs => properties.exists(containsProperty(cs, _)))
41-
}
42-
43-
def mapCSToNum(specifications: Set[ComputationSpecification[A]]): Set[Int] = {
44-
specifications.flatMap(computationSpecificationMap.get)
45-
}
46-
47-
def edgeFunctionForSCCS(node: Int): IntIterator = {
48-
val edges = scheduleGraph.getOrElse(node, Set.empty).iterator
49-
new IntIterator {
50-
def hasNext: Boolean = edges.hasNext
51-
def next(): Int = edges.next()
10+
abstract class IndependentPhaseMergeScheduling extends MaximumPhaseScheduling {
11+
12+
override def mergeIndependentBatches(
13+
batchCount: Int,
14+
dependencyGraph: Map[Int, List[Int]],
15+
nodeIndexMap: Map[Int, List[Int]]
16+
): (Map[Int, List[Int]], Map[Int, List[Int]]) = {
17+
var transformingMap = nodeIndexMap
18+
var counter = batchCount
19+
20+
def mergeIndependentBatches_rek(
21+
graph: Map[Int, List[Int]]
22+
): Map[Int, List[Int]] = {
23+
24+
def getUses(batch: Int, graph: Map[Int, List[Int]]): Set[Int] = {
25+
val directUses = graph.getOrElse(batch, List.empty).toSet
26+
val recursiveUses = directUses.flatMap(otherBatch => getUses(otherBatch, graph))
27+
directUses ++ recursiveUses
5228
}
53-
}
54-
55-
def getAllUses(css: List[Int]): Set[PropertyBounds] = {
56-
var allUses: Set[PropertyBounds] = Set.empty
57-
css.foreach { cs => allUses = allUses ++ computationSpecificationArray(cs).uses(ps) }
58-
allUses
59-
}
60-
61-
def setLazyInAllBatches(
62-
tmp_aCyclicGraph: Map[List[Int], Set[Int]],
63-
firstElement: List[Int]
64-
): Map[List[Int], Set[Int]] = {
65-
var visited_batches: List[List[Int]] = List.empty
66-
var aCyclicGraph = tmp_aCyclicGraph
67-
68-
def setLazyInAllBatches_rek(
69-
tmp_aCyclicGraphInternal: Map[List[Int], Set[Int]],
70-
firstElement: List[Int]
71-
): Map[List[Int], Set[Int]] = {
7229

73-
if (firstElement.forall(csID =>
74-
computationSpecificationArray(csID).computationType.equals(LazyComputation) ||
75-
computationSpecificationArray(csID).computationType.equals(Transformer)
76-
)
77-
) {
78-
var existInSomeBatch = false
79-
tmp_aCyclicGraphInternal.foreach { batch =>
80-
if (batch._2.toList.intersect(firstElement).nonEmpty && batch._1 != firstElement) {
81-
aCyclicGraph = aCyclicGraph + ((batch._1 ++ firstElement) -> mapCSToNum(
82-
getAllCSFromPropertyBounds(getAllUses(batch._1 ++ firstElement))
83-
).diff((batch._1 ++ firstElement).toSet))
84-
aCyclicGraph = aCyclicGraph - batch._1
85-
existInSomeBatch = true
86-
}
87-
}
88-
if (existInSomeBatch) {
89-
aCyclicGraph = aCyclicGraph - firstElement
90-
setLazyInAllBatches_rek(aCyclicGraph, aCyclicGraph.head._1)
91-
} else {
92-
visited_batches = visited_batches :+ firstElement
93-
val keyList = aCyclicGraph.keys.toSet -- visited_batches
94-
if (keyList.nonEmpty) {
95-
aCyclicGraph = setLazyInAllBatches_rek(aCyclicGraph, keyList.head)
96-
}
97-
}
98-
} else {
99-
visited_batches = visited_batches :+ firstElement
100-
val keyList = aCyclicGraph.keys.toSet -- visited_batches
101-
if (keyList.nonEmpty) {
102-
setLazyInAllBatches_rek(aCyclicGraph, keyList.head)
103-
}
104-
}
105-
aCyclicGraph
30+
var map: Map[Int, Set[Int]] = Map.empty
31+
graph.foreach { batch =>
32+
val tempUses = getUses(batch._1, graph)
33+
map = map + (batch._1 -> tempUses)
10634
}
107-
setLazyInAllBatches_rek(aCyclicGraph, firstElement)
108-
}
109-
110-
def mergeIndependentBatches(
111-
nodeIndexMap: Map[Int, List[Int]],
112-
batchCount: Int,
113-
dependencyGraph: Map[Int, List[Int]]
114-
): (Map[Int, List[Int]], Map[Int, List[Int]]) = {
115-
var transformingMap = nodeIndexMap
116-
var counter = batchCount
117-
118-
def mergeIndependentBatches_rek(
119-
graph: Map[Int, List[Int]]
120-
): Map[Int, List[Int]] = {
121-
122-
var allUses: Set[Int] = Set.empty
123-
124-
def getUses(batch: Int): Set[Int] = {
125-
val uses = graph.get(batch).head
126-
allUses = allUses ++ uses
127-
128-
uses.foreach { otherBatch => getUses(otherBatch) }
129-
130-
val returnUses = allUses
131-
returnUses
132-
}
133-
134-
var map: Map[Int, Set[Int]] = Map.empty
135-
graph.foreach { batch =>
136-
val tempUses = getUses(batch._1)
137-
map = map + (batch._1 -> tempUses)
138-
allUses = Set.empty
139-
}
140-
141-
var couldBeMerged: List[(Int, Int)] = List.empty
142-
map.foreach { batch =>
143-
map.foreach { subBatch =>
144-
if (subBatch != batch) {
145-
if ((!subBatch._2.contains(batch._1)) && (!batch._2.contains(subBatch._1))) {
146-
if (!couldBeMerged.contains((subBatch._1, batch._1))) {
147-
couldBeMerged = couldBeMerged :+ (batch._1, subBatch._1)
148-
}
14935

36+
var couldBeMerged: List[(Int, Int)] = List.empty
37+
map.foreach { batch =>
38+
map.foreach { subBatch =>
39+
if (subBatch != batch) {
40+
if ((!subBatch._2.contains(batch._1)) && (!batch._2.contains(subBatch._1))) {
41+
if (!couldBeMerged.contains((subBatch._1, batch._1))) {
42+
couldBeMerged = couldBeMerged :+ (batch._1, subBatch._1)
15043
}
151-
}
152-
153-
}
154-
155-
}
156-
157-
var updatedGraph: Map[Int, List[Int]] = graph
158-
if (couldBeMerged.nonEmpty) {
159-
val tempTransformation_2 =
160-
(transformingMap.get(couldBeMerged.head._1).head ++
161-
transformingMap.get(couldBeMerged.head._2).head).distinct
162-
transformingMap =
163-
transformingMap - couldBeMerged.head._1 - couldBeMerged.head._2
164-
transformingMap = transformingMap + (counter -> tempTransformation_2)
16544

166-
val tempGraph_2: List[Int] = (graph.get(couldBeMerged.head._1).head ++
167-
graph.get(couldBeMerged.head._2).head).distinct
168-
updatedGraph = updatedGraph - couldBeMerged.head._1 - couldBeMerged.head._2
169-
updatedGraph = updatedGraph + (counter -> tempGraph_2)
170-
171-
def replaceIdInMap(oldId: Int, newId: Int): Unit = {
172-
updatedGraph = updatedGraph.map { case (key, values) =>
173-
key -> values.map(v => if (v == oldId) newId else v)
17445
}
17546
}
17647

177-
replaceIdInMap(couldBeMerged.head._1, counter)
178-
replaceIdInMap(couldBeMerged.head._2, counter)
179-
counter = counter + 1
180-
updatedGraph = mergeIndependentBatches_rek(updatedGraph)
18148
}
182-
updatedGraph
183-
}
184-
(mergeIndependentBatches_rek(dependencyGraph), transformingMap)
185-
}
18649

187-
computationSpecificationMap.foreach { csID =>
188-
scheduleGraph += (csID._2 -> mapCSToNum(getAllCSFromPropertyBounds(csID._1.uses(ps))))
189-
}
190-
191-
if (!scheduleLazyTransformerInAllBatches) {
192-
scheduleGraph.foreach { node =>
193-
if (computationSpecificationArray(node._1).computationType.equals(
194-
LazyComputation
195-
) || computationSpecificationArray(node._1).computationType.equals(Transformer)
196-
) {
197-
scheduleGraph.foreach { subNode =>
198-
if (subNode._2.contains(node._1)) {
199-
scheduleGraph =
200-
scheduleGraph +
201-
(node._1 -> (scheduleGraph.get(node._1).head ++ Set(subNode._1)))
202-
}
203-
}
204-
}
20550
}
206-
}
20751

208-
var aCyclicGraph = sccs(scheduleGraph.size, edgeFunctionForSCCS)
209-
.map(batch => batch -> mapCSToNum(getAllCSFromPropertyBounds(getAllUses(batch))))
210-
.toMap
211-
212-
if (scheduleLazyTransformerInAllBatches) {
213-
aCyclicGraph = setLazyInAllBatches(aCyclicGraph, aCyclicGraph.head._1)
214-
}
52+
var updatedGraph: Map[Int, List[Int]] = graph
53+
if (couldBeMerged.nonEmpty) {
54+
val toBeMerged = nextToMerge(couldBeMerged, transformingMap)
21555

216-
val graphWithoutSelfDependencies = aCyclicGraph.map { case (nodes, deps) =>
217-
nodes -> (deps -- nodes).toList
218-
}
56+
val tempTransformation_2 = (transformingMap.get(toBeMerged._1).head ++
57+
transformingMap.get(toBeMerged._2).head).distinct
58+
transformingMap =
59+
transformingMap - toBeMerged._1 - toBeMerged._2
60+
transformingMap = transformingMap + (counter -> tempTransformation_2)
21961

220-
var nodeIndexMap: Map[Int, List[Int]] = Map.empty
221-
var counter = 0
222-
graphWithoutSelfDependencies.foreach { node =>
223-
nodeIndexMap = nodeIndexMap + (counter -> node._1)
224-
counter = counter + 1
225-
}
62+
val tempGraph_2: List[Int] = (graph.get(toBeMerged._1).head ++
63+
graph.get(toBeMerged._2).head).distinct
64+
updatedGraph = updatedGraph - toBeMerged._1 - toBeMerged._2
65+
updatedGraph = updatedGraph + (counter -> tempGraph_2)
22666

227-
var transformedGraph = graphWithoutSelfDependencies.map { case (node, deps) =>
228-
var dependencies: List[Int] = List.empty
229-
nodeIndexMap.foreach { tuple =>
230-
if (tuple._2.intersect(deps).nonEmpty) {
231-
dependencies = dependencies :+ tuple._1
67+
def replaceIdInMap(oldId: Int, newId: Int): Unit = {
68+
updatedGraph = updatedGraph.map { case (key, values) =>
69+
key -> values.map(v => if (v == oldId) newId else v)
70+
}
23271
}
233-
}
234-
nodeIndexMap.find(_._2 == node).map(_._1).head -> dependencies
235-
}
236-
237-
val (newGraph, newTransformingMap) =
238-
mergeIndependentBatches(nodeIndexMap, counter, transformedGraph)
239-
transformedGraph = newGraph
240-
nodeIndexMap = newTransformingMap
241-
242-
val batchOrder = topologicalSort(transformedGraph)
243-
244-
var scheduleBatches: List[PhaseConfiguration[A]] = List.empty
24572

246-
var alreadyScheduledCS: Set[ComputationSpecification[A]] = Set.empty
247-
batchOrder.foreach { batch =>
248-
var scheduledInThisPhase: Set[ComputationSpecification[A]] = Set.empty
249-
nodeIndexMap.get(batch).head.foreach { csID =>
250-
scheduledInThisPhase =
251-
scheduledInThisPhase + computationSpecificationArray(csID)
73+
replaceIdInMap(toBeMerged._1, counter)
74+
replaceIdInMap(toBeMerged._2, counter)
75+
counter = counter + 1
76+
updatedGraph = mergeIndependentBatches_rek(updatedGraph)
25277
}
253-
254-
scheduleBatches = scheduleBatches :+ computePhase(
255-
ps,
256-
scheduledInThisPhase,
257-
allCS -- scheduledInThisPhase -- alreadyScheduledCS
258-
)
259-
alreadyScheduledCS = alreadyScheduledCS ++ scheduledInThisPhase
78+
updatedGraph
26079
}
80+
(mergeIndependentBatches_rek(dependencyGraph), transformingMap)
81+
}
26182

262-
scheduleBatches
83+
def nextToMerge(couldBeMerged: List[(Int, Int)], transformingMap: Map[Int, List[Int]]): (Int, Int) = {
84+
couldBeMerged.head
26385
}
26486
}
26587

266-
object IndependentPhaseMergeScheduling {
267-
val name = "IPMS"
268-
}
88+
object IndependentPhaseMergeScheduling extends IndependentPhaseMergeScheduling

0 commit comments

Comments
 (0)