Skip to content

Implementation of several scheduling methods to improve the performance of static analyses #263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 67 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
aa8d780
Fixing Error with "EagerFieldAccessInformationAnalysis", so the analy…
19Vik19 Sep 5, 2024
0d3ba57
Next Attempt
19Vik19 Sep 8, 2024
3edb263
Revert "Next Attempt"
19Vik19 Sep 8, 2024
3734dc6
Bereit für nächsten Versuch mit graph und zyklen finden ...
19Vik19 Oct 27, 2024
72b54c1
First completed approach
19Vik19 Dec 2, 2024
e72ffbd
Alle Strategien bis auf strategie 3
19Vik19 Dec 12, 2024
6ed388a
Strategie 3 funktioniert, tests wurden in PropertyComputationsSchedul…
19Vik19 Dec 20, 2024
1c1d60d
.
19Vik19 Dec 23, 2024
8d3bcb2
Merge branch '02122024' into lastbranch2024
19Vik19 Dec 23, 2024
e9a16fd
Fieldimmutability dependencies fix
19Vik19 Dec 23, 2024
b67b8c9
Immutability fix after merge
19Vik19 Dec 24, 2024
b5369ce
xcorpus runner
19Vik19 Dec 30, 2024
15f87cd
Neues Jahr neues Glück :)
19Vik19 Jan 1, 2025
7112bd4
constructor CallGraph in class CallGraph is not private anymore. For …
19Vik19 Jan 17, 2025
b885136
Prepared for testing strategie 3 true
19Vik19 Jan 18, 2025
a4bc582
Last Check
19Vik19 Jan 18, 2025
549c800
Small rollbacks
19Vik19 Jan 18, 2025
9dd0e39
Merge branch 'develop' into AUFGERÄUMT&GEUPDATED190125
19Vik19 Jan 19, 2025
091a44b
Minor changes, cleanup
19Vik19 Jan 19, 2025
222bd23
Cleanup
19Vik19 Jan 19, 2025
ac69a41
Cleanup
19Vik19 Jan 19, 2025
64783db
Documentation config
19Vik19 Jan 19, 2025
a97eef9
Bug fixes
19Vik19 Jan 20, 2025
b581e28
Cleanup and config with string-based strategy
19Vik19 Jan 20, 2025
e6f08b4
Created new Runner for immutability and Purity to schedule all analys…
19Vik19 Jan 20, 2025
688ea35
Cleanup
19Vik19 Jan 21, 2025
ba21166
Merge branch 'AUFGERÄUMT&GEUPDATED190125' into Preparation_for_the_me…
19Vik19 Jan 21, 2025
2e1f95f
Cleanup
19Vik19 Jan 21, 2025
fd3e6fa
Documentation
19Vik19 Jan 21, 2025
93e5369
Implementation of an extra check to prevent a NullPointerException fo…
19Vik19 Jan 21, 2025
a5e777c
Changed Config option “fpcf.AnalysisScenario.ScheduleStrategy” to Cla…
19Vik19 Jan 21, 2025
10ecf4f
Added config for measurement (ImmutabilityRunner - MPS - true)
19Vik19 Jan 21, 2025
517f288
Improvements
19Vik19 Jan 21, 2025
17f8770
SPS Scheduling Problem - Debugging branch
19Vik19 Feb 1, 2025
beaeaab
Formatting
Feb 3, 2025
dce5ad2
Formating
19Vik19 Feb 26, 2025
88c1e7b
Merge branch 'develop' into afterBA
19Vik19 Feb 26, 2025
5d89f47
Formatting
19Vik19 Mar 11, 2025
1767f13
Formatting
19Vik19 Mar 22, 2025
e0a44d9
Improvements, OPAL-standard config implementation
19Vik19 Mar 23, 2025
c369bbc
Minor improvements
19Vik19 Mar 23, 2025
4d5e53a
Added code documentation. Small fixes
19Vik19 Mar 23, 2025
2bb7618
reset test cases
19Vik19 Apr 18, 2025
9d1ad3b
.
19Vik19 May 6, 2025
62e5b39
Cleanup for PR
19Vik19 Jun 6, 2025
c33391e
Merge branch 'develop' of https://github.com/19Vik19/opal_bachelorarb…
19Vik19 Jun 6, 2025
40cdb64
Formatting, implementation SchdulingStrategy
19Vik19 Jun 9, 2025
b151a8f
Refactor: Remove unnecessary value declaration, enhance test implemen…
19Vik19 Jun 9, 2025
34a4e17
headerCheck
19Vik19 Jun 9, 2025
2dc04e7
Add command alias for format checking
Jul 9, 2025
74d6746
Sample pre-commit hook script for formatting
Jul 9, 2025
5f5c407
Merge branch 'develop' into scheduling-strategies
Jul 9, 2025
9185cca
Merge branch 'feature/format-check' into scheduling-strategies
Jul 9, 2025
011fed8
Add FIXME for subphase finalization order
Jul 9, 2025
caca611
Fix wrong package declarations and imports
Jul 9, 2025
352a653
Fix Scaladoc for scheduling strategies
Jul 9, 2025
63f1e46
Factor out topologicalSort
Jul 9, 2025
f078658
Remove spurious inheritance
Jul 9, 2025
89f3a5d
Factor out common code
Jul 9, 2025
5549e7b
Load scheduling strategy reflectively
Jul 9, 2025
eb74518
Improve PropertyComputationsSchedulerTest
Jul 9, 2025
80cf414
Improve AnalysisScenario config
Jul 11, 2025
606a5ec
Improve phase merging
Jul 11, 2025
8b29482
Fix typos in test
Jul 11, 2025
b362105
Fix test
Jul 11, 2025
62a94a3
Improve scheduling strategies
Jul 14, 2025
9403a91
Re-apply dependency management fix
Jul 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org
package opalj
package org.opalj
package fpcf
package analyses

Expand All @@ -19,10 +18,6 @@ import org.opalj.br.fpcf.properties.immutability.MutableType
import org.opalj.br.fpcf.properties.immutability.NonTransitivelyImmutableType
import org.opalj.br.fpcf.properties.immutability.TransitivelyImmutableType
import org.opalj.br.fpcf.properties.immutability.TypeImmutability
import org.opalj.fpcf.Entity
import org.opalj.fpcf.EPS
import org.opalj.fpcf.FPCFAnalysesManagerKey
import org.opalj.fpcf.PropertyStore
import org.opalj.tac.cg.RTACallGraphKey
import org.opalj.tac.fpcf.analyses.LazyFieldImmutabilityAnalysis
import org.opalj.tac.fpcf.analyses.LazyFieldLocalityAnalysis
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ object Immutability {

var projectTime: Seconds = Seconds.None
var analysisTime: Seconds = Seconds.None
val callGraphTime: Seconds = Seconds.None
var callGraphTime: Seconds = Seconds.None

val project = time {
Project(
Expand Down Expand Up @@ -182,7 +182,9 @@ object Immutability {
val propertyStore = project.get(PropertyStoreKey)
val analysesManager = project.get(FPCFAnalysesManagerKey)

project.get(callgraphKey)
time {
project.get(callgraphKey)
} { t => callGraphTime = t.toSeconds }

time {
analysesManager.runAll(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,9 @@ object InitialEntryPointsKey extends ProjectInformationKey[Iterable[Method], Not
val epFinder = instantiateEntryPointFinder(fqn)
epFinder
}

/**
* Reflectively instantiates a ''ClosedPackagesAnalysis'' for the given project.
* The instantiated class has to satisfy the interface and needs to provide a single
* constructor parameterized over a Project.
* Reflectively instantiates an ''EntryPointFinder'' object.
*/
private[this] def instantiateEntryPointFinder(fqn: String): EntryPointFinder = {
import scala.reflect.runtime.universe._
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org
package opalj
package org.opalj
package concurrent

import java.util.concurrent.{Future => JFuture}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org
package opalj
package org.opalj

import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
Expand Down
36 changes: 36 additions & 0 deletions OPAL/common/src/main/scala/org/opalj/graphs/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -729,4 +729,40 @@ package object graphs {
sccs
}

/**
* Provides a topological order of the given acyclic graph's nodes.
*
* @throws IllegalStateException If the graph contains cycles
*/
def topologicalSort(graph: Map[Int, List[Int]]): List[Int] = {
var sortedNodes: List[Int] = List.empty
var permanent: Set[Int] = Set.empty
var temporary: Set[Int] = Set.empty

val preparedGraph = graph.map { case (node, deps) =>
node -> deps.filter(_ != node)
}

def visit(node: Int): Unit = {
if (!permanent.contains(node)) {
if (temporary.contains(node)) {
throw new IllegalStateException("Graph contains a cycle")
}
temporary = temporary + node

preparedGraph(node).foreach { otherNode => visit(otherNode) }

permanent = permanent + node
temporary = temporary - node

sortedNodes = sortedNodes :+ node
}

}
for (node <- preparedGraph.keys) {
visit(node)
}

sortedNodes
}
}
3 changes: 1 addition & 2 deletions OPAL/common/src/main/scala/org/opalj/util/Return.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org
package opalj
package org.opalj
package util

// TODO: Replace this class by scala.util.control.NonLocalReturns in Scala 3
Expand Down
3 changes: 1 addition & 2 deletions OPAL/common/src/main/scala/org/opalj/util/package.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org
package opalj
package org.opalj

import scala.annotation.nowarn

Expand Down
38 changes: 23 additions & 15 deletions OPAL/si/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
org.opalj {

// Turns on the debugging support of the property store which is primarily meant
// to support the debugging of analyses developed using the property store.
// I.e., debug performs a wide range of additionaly checks to identify errors as
// early as possible.
fpcf.PropertyStore.Debug = false
fpcf.PropertyStore.TraceFallbacks = false
fpcf.PropertyStore.TraceSuppressedNotifications = false
fpcf.PropertyStore.Default = "Parallel"
fpcf {
PropertyStore {
// Turns on the debugging support of the property store which is primarily meant
// to support the debugging of analyses developed using the property store.
// I.e., debug performs a wide range of additionaly checks to identify errors as
// early as possible.
Debug = false
TraceFallbacks = false
TraceSuppressedNotifications = false
Default = "Parallel"
}

// For tasks managers for the seq. store see PKESequentialPropertyStore.Strategies
fpcf.seq.PKESequentialPropertyStore.TasksManager = "ManyDirectDependenciesLast"
fpcf.seq.PKESequentialPropertyStore.MaxEvaluationDepth = 32
// For tasks managers for the seq. store see PKESequentialPropertyStore.Strategies
seq.PKESequentialPropertyStore.TasksManager = "ManyDirectDependenciesLast"
seq.PKESequentialPropertyStore.MaxEvaluationDepth = 32

// For tasks managers for the par. store see PKECPropertyStore.Strategies
fpcf.par.PKECPropertyStore.TasksManager = "NoPriority"
fpcf.par.PKECPropertyStore.MaxEvaluationDepth = 32
// For tasks managers for the par. store see PKECPropertyStore.Strategies
par.PKECPropertyStore.TasksManager = "NoPriority"
par.PKECPropertyStore.MaxEvaluationDepth = 32

fpcf.AnalysisScenario.AnalysisAutoConfig = false
AnalysisScenario {
AnalysisAutoConfig = false
SchedulingStrategy = "org.opalj.fpcf.scheduling.SmallestPhaseMergeScheduling"
ScheduleLazyInMultiplePhases = true
}
}

si.flowanalysis.StructuralAnalysis {
maxIterations = 1000
Expand Down
141 changes: 63 additions & 78 deletions OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
package org.opalj
package fpcf

import com.typesafe.config.Config

import org.opalj.fpcf.AnalysisScenario.AnalysisAutoConfigKey
import org.opalj.fpcf.AnalysisScenario.AnalysisSchedulingStrategyKey
import org.opalj.fpcf.scheduling.SchedulingStrategy
import org.opalj.graphs.Graph
import org.opalj.log.LogContext
import org.opalj.log.OPALLogger
import org.opalj.util.PerformanceEvaluation.time

/**
* Provides functionality to determine whether a set of analyses is compatible and to compute
Expand Down Expand Up @@ -225,103 +230,80 @@ class AnalysisScenario[A](val ps: PropertyStore) {
scheduleComputed = true
}

allCS.foreach(processCS)
val scheduledBatches = if (allCS.isEmpty) List.empty
else time {
allCS.foreach(processCS)

val alreadyComputedPropertyKinds = propertyStore.alreadyComputedPropertyKindIds.toSet
val alreadyComputedPropertyKinds = propertyStore.alreadyComputedPropertyKindIds.toSet

// 0. check that a property was not already derived
allCS.foreach { cs =>
cs.derives foreach { derivedProperty =>
if (alreadyComputedPropertyKinds.contains(derivedProperty.pk.id)) {
val pkName = PropertyKey.name(derivedProperty.pk.id)
val m = s"can not register $cs: $pkName was computed in a previous phase"
throw new SpecificationViolation(m)
// 0. check that a property was not already derived
allCS.foreach { cs =>
cs.derives foreach { derivedProperty =>
if (alreadyComputedPropertyKinds.contains(derivedProperty.pk.id)) {
val pkName = PropertyKey.name(derivedProperty.pk.id)
val m = s"can not register $cs: $pkName was computed in a previous phase"
throw new SpecificationViolation(m)
}
}
}
}

// 1. check for properties that are not derived (and which require an analysis)
def useFallback(underivedProperty: PropertyBounds, propertyName: String) = {
if (PropertyKey.hasFallback(underivedProperty.pk)) {
val message = s"no analyses scheduled for: $propertyName; using fallback"
OPALLogger.warn("analysis configuration", message)
} else {
throw new IllegalStateException(s"no analysis scheduled for $propertyName")
// 1. check for properties that are not derived (and which require an analysis)
def useFallback(underivedProperty: PropertyBounds, propertyName: String) = {
if (PropertyKey.hasFallback(underivedProperty.pk)) {
val message = s"no analyses scheduled for: $propertyName; using fallback"
OPALLogger.warn("analysis configuration", message)
} else {
throw new IllegalStateException(s"no analysis scheduled for $propertyName")
}
}
}

val analysisAutoConfig = BaseConfig.getBoolean(AnalysisAutoConfigKey)
val underivedProperties = usedProperties -- derivedProperties
underivedProperties
.filterNot { underivedProperty => alreadyComputedPropertyKinds.contains(underivedProperty.pk.id) }
.foreach { underivedProperty =>
if (!derivedProperties.contains(underivedProperty)) {
val propertyName = PropertyKey.name(underivedProperty.pk.id)
val defaultCSOpt =
if (analysisAutoConfig) defaultAnalysis(underivedProperty) else None
if (defaultCSOpt.isDefined) {
val defaultCS = defaultCSOpt.get
try {
processCS(defaultCS)
val message = s"no analyses scheduled for: $propertyName; using ${defaultCS.name}"
OPALLogger.info("analysis configuration", message)
} catch {
case _: SpecificationViolation =>
useFallback(underivedProperty, propertyName)
implicit val config = propertyStore.context(classOf[Config])

val analysisAutoConfig = config.getBoolean(AnalysisAutoConfigKey)
val underivedProperties = usedProperties -- derivedProperties
underivedProperties
.filterNot { underivedProperty => alreadyComputedPropertyKinds.contains(underivedProperty.pk.id) }
.foreach { underivedProperty =>
if (!derivedProperties.contains(underivedProperty)) {
val propertyName = PropertyKey.name(underivedProperty.pk.id)
val defaultCSOpt =
if (analysisAutoConfig) defaultAnalysis(underivedProperty) else None
if (defaultCSOpt.isDefined) {
val defaultCS = defaultCSOpt.get
try {
processCS(defaultCS)
val message = s"no analyses scheduled for: $propertyName; using ${defaultCS.name}"
OPALLogger.info("analysis configuration", message)
} catch {
case _: SpecificationViolation =>
useFallback(underivedProperty, propertyName)
}
} else {
useFallback(underivedProperty, propertyName)
}
} else {
useFallback(underivedProperty, propertyName)
}
}
}

// TODO check all further constraints (in particular those related to cyclic dependencies between analysis...)

// 2. assign analyses to different batches if an analysis can only process
// final properties (unless it is a transformer, the latter have special paths and
// constraints and can always be scheduled in the same batch!)
val schedulingStrategy = instantiateSchedulingStrategy(config.getString(AnalysisSchedulingStrategyKey))
OPALLogger.info("scheduler", s"scheduling strategy ${schedulingStrategy} is selected")

// TODO ....
schedulingStrategy.schedule(ps, allCS)
} { t => OPALLogger.info("scheduler", s"computation of schedule took ${t.toSeconds}") }

Schedule(
if (allCS.isEmpty) List.empty else List(computePhase(propertyStore)),
scheduledBatches,
initializationData
)
}

/**
* Computes the configuration for a specific batch; this method can only handle the situation
* where all analyses can be executed in the same phase.
* Reflectively instantiates a ''SchedulingStrategy'' object.
*/
private def computePhase(propertyStore: PropertyStore): PhaseConfiguration[A] = {

// 1. compute the phase configuration; i.e., find those properties for which we must
// suppress interim updates.
var suppressInterimUpdates: Map[PropertyKind, Set[PropertyKind]] = Map.empty
// Interim updates have to be suppressed when an analysis uses a property for which
// the wrong bounds/not enough bounds are computed.
transformersCS foreach { cs => suppressInterimUpdates += (cs.derivesLazily.get.pk -> cs.uses(ps).map(_.pk)) }

// 3. create the batch
val batchBuilder = List.newBuilder[ComputationSpecification[A]]
batchBuilder ++= lazyCS
batchBuilder ++= transformersCS
batchBuilder ++= triggeredCS
batchBuilder ++= eagerCS

// FIXME...

// Interim updates can be suppressed when the depender and dependee are not in a cyclic
// relation; however, this could have a negative impact on the effect of deep laziness -
// once we are actually implementing it. For the time being, suppress notifications is always
// advantageous.

val phase1Configuration = PropertyKindsConfiguration(
propertyKindsComputedInThisPhase = derivedProperties.map(_.pk),
suppressInterimUpdates = suppressInterimUpdates
)

PhaseConfiguration(phase1Configuration, batchBuilder.result())
private[this] def instantiateSchedulingStrategy(fqn: String): SchedulingStrategy = {
import scala.reflect.runtime.universe._
val mirror = runtimeMirror(this.getClass.getClassLoader)
val module = mirror.staticModule(fqn)
mirror.reflectModule(module).instance.asInstanceOf[SchedulingStrategy]
}
}

Expand All @@ -330,7 +312,10 @@ class AnalysisScenario[A](val ps: PropertyStore) {
*/
object AnalysisScenario {

final val AnalysisAutoConfigKey = "org.opalj.fpcf.AnalysisScenario.AnalysisAutoConfig"
final val ConfigKeyPrefix = "org.opalj.fpcf.AnalysisScenario."

final val AnalysisAutoConfigKey = s"${ConfigKeyPrefix}AnalysisAutoConfig"
final val AnalysisSchedulingStrategyKey = s"${ConfigKeyPrefix}SchedulingStrategy"

/**
* @param analyses The set of analyses that should be executed as part of this analysis scenario.
Expand Down
16 changes: 11 additions & 5 deletions OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -619,14 +619,20 @@ abstract class PropertyStore {
// Step 4
// Save the information about the finalization order (of properties which are
// collaboratively computed).
val cleanUpSubPhase =
(propertyKindsComputedInThisPhase -- finalizationOrder.flatten.toSet) + AnalysisKey
this.subPhaseFinalizationOrder =
val cleanUpSubPhase = propertyKindsComputedInThisPhase -- finalizationOrder.flatten.toSet

val finalizationWithAnalysisKey = finalizationOrder.zipWithIndex.map {
case (subPhase, 0) => subPhase :+ AnalysisKey // Add AnalysisKey to first sub-phase
case (subPhase, _) => subPhase // Keep other sub-phases unchanged
}

this.subPhaseFinalizationOrder = {
if (cleanUpSubPhase.isEmpty) {
finalizationOrder.toArray
finalizationWithAnalysisKey
} else {
(finalizationOrder :+ cleanUpSubPhase.toList).toArray
finalizationWithAnalysisKey :+ cleanUpSubPhase.toList
}
}.toArray

subPhaseId = 0
hasSuppressedNotifications = suppressInterimUpdates.nonEmpty
Expand Down
Loading