From 247966a3f66b13e908c152f0ca429f55b71318ae Mon Sep 17 00:00:00 2001 From: Julia Rossow <stu222169@mail.uni-kiel.de> Date: Wed, 8 Dec 2021 16:41:46 +0100 Subject: [PATCH] New strategy InitialGuessSearchStrategy --- .../strategies/searchstrategy/BinarySearch.kt | 8 +- .../searchstrategy/CompositeStrategy.kt | 3 +- .../strategies/searchstrategy/FullSearch.kt | 7 +- .../InitialGuessSearchStrategy.kt | 60 ++++++++++++ .../strategies/searchstrategy/LinearSearch.kt | 8 +- .../searchstrategy/SearchStrategy.kt | 2 +- .../InitialGuessSearchStrategyTest.kt | 97 +++++++++++++++++++ 7 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt create mode 100644 theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt index 28e8194c6..d2298fe6f 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt @@ -13,7 +13,13 @@ private val logger = KotlinLogging.logger {} * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. */ class BinarySearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { + override fun findSuitableResource(load: LoadDimension, resources: List<Resource>, + lastLowestResource: Resource?): Resource? { + + if (lastLowestResource != null) { + logger.info { "Running LinearSearch with a set lastLowestResource value doesn't make sense." } + } + val result = binarySearch(load, resources, 0, resources.size - 1) if (result == -1) { return null diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt index 41cc5c325..777a87c15 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt @@ -20,7 +20,8 @@ class CompositeStrategy( val restrictionStrategies: Set<RestrictionStrategy> ) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { + override fun findSuitableResource(load: LoadDimension, resources: List<Resource>, + lastLowestResource: Resource?): Resource? { var restrictedResources = resources.toList() for (strategy in this.restrictionStrategies) { restrictedResources = restrictedResources.intersect(strategy.apply(load, resources)).toList() diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt index cb0dd2d8a..3a58bd29a 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt @@ -17,7 +17,12 @@ private val logger = KotlinLogging.logger {} */ class FullSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { + override fun findSuitableResource(load: LoadDimension, resources: List<Resource>, + lastLowestResource: Resource?): Resource? { + if (lastLowestResource != null) { + logger.info { "Running LinearSearch with a set lastLowestResource value doesn't make sense." } + } + var minimalSuitableResources: Resource? = null for (res in resources) { logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt new file mode 100644 index 000000000..7efeea05d --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt @@ -0,0 +1,60 @@ +package theodolite.strategies.searchstrategy + +import mu.KotlinLogging +import theodolite.execution.BenchmarkExecutor +import theodolite.util.LoadDimension +import theodolite.util.Resource + +private val logger = KotlinLogging.logger {} + +/** + * Linear-search-like implementation for determining the smallest suitable number of instances. + * + * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. + */ +class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { + + override fun findSuitableResource(load: LoadDimension, resources: List<Resource>, lastLowestResource: Resource?): Resource? { + + if (lastLowestResource != null) { + val resourcesToCheck: List<Resource> + val startIndex: Int = resources.indexOf(lastLowestResource) + + logger.info { "Running experiment with load '${load.get()}' and resources '${lastLowestResource.get()}'" } + + if (this.benchmarkExecutor.runExperiment(load, lastLowestResource)) { + + resourcesToCheck = resources.subList(0, startIndex).reversed() + if(resourcesToCheck.isEmpty()) return lastLowestResource + + var currentMin : Resource = lastLowestResource + for (res in resourcesToCheck) { + + logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } + if (this.benchmarkExecutor.runExperiment(load, res)) { + currentMin = res + } + } + return currentMin + } + else { + if (resources.size <= startIndex + 1) { + logger.info{ "No more resources left to check." } + return null + } + resourcesToCheck = resources.subList(startIndex + 1, resources.size) + + for (res in resourcesToCheck) { + + logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } + if (this.benchmarkExecutor.runExperiment(load, res)) return res + } + } + } + else { + logger.info { "InitialGuessSearchStrategy called without lastLowestResource value, which is needed as a " + + "starting point!" } + } + return null + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt index 85deaf6fa..09f431e9f 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt @@ -14,12 +14,18 @@ private val logger = KotlinLogging.logger {} */ class LinearSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { + override fun findSuitableResource(load: LoadDimension, resources: List<Resource>, + lastLowestResource: Resource?): Resource? { + if (lastLowestResource != null) { + logger.info { "Running LinearSearch with a set lastLowestResource value doesn't make sense." } + } + for (res in resources) { logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } if (this.benchmarkExecutor.runExperiment(load, res)) return res } + return null } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt index 4e304b010..b7994617b 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt @@ -20,5 +20,5 @@ abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor) { * * @return suitable resource for the specified load, or null if no suitable resource exists. */ - abstract fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? + abstract fun findSuitableResource(load: LoadDimension, resources: List<Resource>, lastLowestResource: Resource? = null): Resource? } diff --git a/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt new file mode 100644 index 000000000..0397905d0 --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt @@ -0,0 +1,97 @@ +package theodolite + +import io.quarkus.test.junit.QuarkusTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import theodolite.benchmark.BenchmarkExecution +import theodolite.strategies.searchstrategy.InitialGuessSearchStrategy +import theodolite.util.LoadDimension +import theodolite.util.Resource +import theodolite.util.Results +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {} + +@QuarkusTest +class InitialGuessSearchStrategyTest { + + @Test + fun testEnd2EndInitialGuessSearch() { + val mockResults = arrayOf( + arrayOf(true, true, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true), + arrayOf(false, false, false, true, true, true, true), + arrayOf(false, false, false, false, true, true, true), + arrayOf(false, false, false, false, false, false, true), + arrayOf(false, false, false, false, false, false, false) + ) + val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) } + val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } + val results = Results() + val benchmark = TestBenchmark() + val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() + val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, sloChecker, 0, 0, 5) + val strategy = InitialGuessSearchStrategy(benchmarkExecutor) + + val actual: ArrayList<Resource?> = ArrayList() + val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) }) + expected.add(null) + + var currentResource : Resource? = mockResources[0] + for (load in mockLoads) { + val returnVal : Resource? = strategy.findSuitableResource(load, mockResources, currentResource) + if(returnVal != null) { + logger.info { "returnVal '${returnVal.get()}'" } + } + else { + logger.info { "returnVal is null." } + } + + actual.add(returnVal) + currentResource = returnVal + } + + assertEquals(actual, expected) + } + + @Test + fun testEnd2EndInitialGuessSearchLowerResourceDemandHigherLoad() { + val mockResults = arrayOf( + arrayOf(true, true, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true), + arrayOf(false, true, true, true, true, true, true), + arrayOf(false, false, false, false, true, true, true), + arrayOf(false, false, false, false, false, false, true), + arrayOf(false, false, false, false, false, false, false) + ) + val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) } + val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } + val results = Results() + val benchmark = TestBenchmark() + val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() + val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, sloChecker, 0, 0, 5) + val strategy = InitialGuessSearchStrategy(benchmarkExecutor) + + val actual: ArrayList<Resource?> = ArrayList() + val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 1, 4, 6).map { x -> Resource(x, emptyList()) }) + expected.add(null) + + var currentResource : Resource? = mockResources[0] + for (load in mockLoads) { + val returnVal : Resource? = strategy.findSuitableResource(load, mockResources, currentResource) + if(returnVal != null) { + logger.info { "returnVal '${returnVal.get()}'" } + } + else { + logger.info { "returnVal is null." } + } + + actual.add(returnVal) + currentResource = returnVal + } + + assertEquals(actual, expected) + } +} \ No newline at end of file -- GitLab