Skip to content

Commit 811fcdf

Browse files
Removing 'task groups' from background tasks memory benchmark and fixing a bug in average computation
1 parent fa9b570 commit 811fcdf

13 files changed

Lines changed: 311 additions & 176 deletions

File tree

app/src/main/java/com/techyourchance/android/backgroundtasksbenchmark/memory/BackgroundTaskGroupsMemoryResult.kt

Lines changed: 0 additions & 12 deletions
This file was deleted.

app/src/main/java/com/techyourchance/android/backgroundtasksbenchmark/memory/BackgroundTasksMemoryBenchmarkUseCase.kt

Lines changed: 53 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import java.util.concurrent.CyclicBarrier
2020
import java.util.concurrent.SynchronousQueue
2121
import java.util.concurrent.ThreadPoolExecutor
2222
import java.util.concurrent.TimeUnit
23-
import java.util.concurrent.atomic.AtomicInteger
2423
import javax.inject.Inject
2524
import kotlin.coroutines.coroutineContext
2625
import kotlin.coroutines.resume
@@ -36,10 +35,9 @@ class BackgroundTasksMemoryBenchmarkUseCase @Inject constructor(
3635

3736
data class Result(
3837
val isCompleteResult: Boolean,
39-
val numTasksInGroup: Int,
40-
val threadsResult: BackgroundTaskGroupsMemoryResult,
41-
val coroutinesResult: BackgroundTaskGroupsMemoryResult,
42-
val threadPoolResult: BackgroundTaskGroupsMemoryResult,
38+
val threadsResult: BackgroundTasksMemoryResult,
39+
val coroutinesResult: BackgroundTasksMemoryResult,
40+
val threadPoolResult: BackgroundTasksMemoryResult,
4341
)
4442

4543
suspend fun runBenchmark(benchmarkPhase: BackgroundTasksMemoryBenchmarkPhase, benchmarkIterationNum: Int): Result {
@@ -69,26 +67,24 @@ class BackgroundTasksMemoryBenchmarkUseCase @Inject constructor(
6967
return@withContext if (isBenchmarkCompleted) {
7068
Result(
7169
true,
72-
NUM_TASKS_IN_GROUP,
7370
fetchBackgroundTaskMemoryDataUseCase.fetchData(LABEL_THREADS),
7471
fetchBackgroundTaskMemoryDataUseCase.fetchData(LABEL_COROUTINES),
7572
fetchBackgroundTaskMemoryDataUseCase.fetchData(LABEL_THREAD_POOL),
7673
)
7774
} else {
7875
Result(
7976
false,
80-
0,
81-
BackgroundTaskGroupsMemoryResult.NULL_OBJECT,
82-
BackgroundTaskGroupsMemoryResult.NULL_OBJECT,
83-
BackgroundTaskGroupsMemoryResult.NULL_OBJECT,
77+
BackgroundTasksMemoryResult.NULL_OBJECT,
78+
BackgroundTasksMemoryResult.NULL_OBJECT,
79+
BackgroundTasksMemoryResult.NULL_OBJECT,
8480
)
8581
}
8682
}
8783
}
8884

8985
private fun preAllocateIterationData(): ConcurrentHashMap<Int, BackgroundTaskMemoryData> {
90-
val iterationData = ConcurrentHashMap<Int, BackgroundTaskMemoryData>(NUM_TASK_GROUPS)
91-
for (taskNum in 0 until NUM_TASK_GROUPS) {
86+
val iterationData = ConcurrentHashMap<Int, BackgroundTaskMemoryData>(NUM_TASKS)
87+
for (taskNum in 0 until NUM_TASKS) {
9288
iterationData[taskNum] = BackgroundTaskMemoryData.NULL_OBJECT
9389
}
9490
return iterationData
@@ -100,46 +96,37 @@ class BackgroundTasksMemoryBenchmarkUseCase @Inject constructor(
10096
): Boolean {
10197
MyLogger.i("benchmarkThreads(); iteration: $iterationNum")
10298
val startedThreads = mutableListOf<Thread>()
103-
val threadBarrier = CyclicBarrier(NUM_TASK_GROUPS * NUM_TASKS_IN_GROUP + 1)
104-
for (taskGroupNum in 0 until NUM_TASK_GROUPS) {
99+
val threadBarrier = CyclicBarrier(NUM_TASKS + 1)
100+
for (taskNum in 0 until NUM_TASKS) {
105101
coroutineContext.ensureActive()
106-
val groupStartedThreads = benchmarkSingleThreadsGroup(
107-
iterationNum, taskGroupNum, iterationData, threadBarrier
102+
val thread = benchmarkSingleThread(
103+
iterationNum, taskNum, iterationData, threadBarrier
108104
)
109-
startedThreads.addAll(groupStartedThreads)
105+
startedThreads.add(thread)
110106
}
111107
threadBarrier.await()
112108
startedThreads.forEach { it.join() }
113109
saveBackgroundTaskMemoryDataUseCase.saveData(LABEL_THREADS, iterationNum, iterationData)
114110
return !restartAppForNextIteration(BackgroundTasksMemoryBenchmarkPhase.THREADS, iterationNum)
115111
}
116112

117-
private suspend fun benchmarkSingleThreadsGroup(
113+
private suspend fun benchmarkSingleThread(
118114
iterationNum: Int,
119-
taskGroupNum: Int,
115+
taskNum: Int,
120116
threadsData: MutableMap<Int, BackgroundTaskMemoryData>,
121117
threadBarrier: CyclicBarrier
122-
): List<Thread> {
123-
val groupThreads = mutableListOf<Thread>()
124-
suspendCoroutine { continuation ->
125-
val groupThreadBarrier = CyclicBarrier(NUM_TASKS_IN_GROUP)
126-
for (taskInGroup in 0 until NUM_TASKS_IN_GROUP) {
127-
Thread {
128-
MyLogger.d("Thread started; iteration: $iterationNum; group $taskGroupNum; task $taskInGroup")
129-
groupThreadBarrier.await() // make sure all threads started
130-
if (taskInGroup == NUM_TASKS_IN_GROUP - 1) {
131-
threadsData[taskGroupNum] = BackgroundTaskMemoryData(getAppMemoryConsumption())
132-
continuation.resume(Unit)
133-
}
134-
threadBarrier.await()
135-
MyLogger.d("Thread terminates; iteration: $iterationNum; group $taskGroupNum; task $taskInGroup")
136-
}.apply {
137-
start()
138-
groupThreads.add(this)
139-
}
118+
): Thread {
119+
return suspendCoroutine { continuation ->
120+
val thread = Thread {
121+
MyLogger.d("Thread started; iteration: $iterationNum; task $taskNum")
122+
threadsData[taskNum] = BackgroundTaskMemoryData(getAppMemoryConsumption())
123+
continuation.resume(Thread.currentThread())
124+
threadBarrier.await()
125+
MyLogger.d("Thread terminates; iteration: $iterationNum; task $taskNum")
126+
}.apply {
127+
start()
140128
}
141129
}
142-
return groupThreads
143130
}
144131

145132
private suspend fun benchmarkCoroutines(
@@ -156,10 +143,10 @@ class BackgroundTasksMemoryBenchmarkUseCase @Inject constructor(
156143

157144
MyLogger.i("benchmarkCoroutines(); iteration: $iterationNum")
158145
val awaitFlow = MutableSharedFlow<Unit>()
159-
for (taskGroupNum in 0 until NUM_TASK_GROUPS) {
146+
for (taskNum in 0 until NUM_TASKS) {
160147
coroutineContext.ensureActive()
161-
benchmarkSingleCoroutineGroup(
162-
iterationNum, taskGroupNum, iterationData, awaitFlow, coroutinesScope
148+
benchmarkSingleCoroutine(
149+
iterationNum, taskNum, iterationData, awaitFlow, coroutinesScope
163150
)
164151
}
165152
awaitFlow.emit(Unit)
@@ -171,43 +158,26 @@ class BackgroundTasksMemoryBenchmarkUseCase @Inject constructor(
171158
return !restartAppForNextIteration(BackgroundTasksMemoryBenchmarkPhase.COROUTINES, iterationNum)
172159
}
173160

174-
private suspend fun benchmarkSingleCoroutineGroup(
161+
private suspend fun benchmarkSingleCoroutine(
175162
iterationNum: Int,
176-
taskGroupNum: Int,
163+
taskNum: Int,
177164
iterationData: MutableMap<Int, BackgroundTaskMemoryData>,
178165
awaitFlow: MutableSharedFlow<Unit>,
179166
coroutinesScope: CoroutineScope,
180167
) {
181168
suspendCoroutine { continuation ->
182-
val groupLaunchedCounter = AtomicInteger(0)
183-
val groupAwaitFlow = MutableSharedFlow<Unit>()
184-
for (taskInGroup in 0 until NUM_TASKS_IN_GROUP) {
185-
coroutinesScope.launch {
186-
MyLogger.d("Coroutine launched; iteration: $iterationNum; group $taskGroupNum; task $taskInGroup")
187-
if(groupLaunchedCounter.incrementAndGet() == NUM_TASKS_IN_GROUP) {
188-
groupAwaitFlow.emit(Unit)
189-
} else {
190-
try {
191-
groupAwaitFlow.collect {
192-
throw CancellationException()
193-
}
194-
} catch (e: CancellationException) {
195-
// no-op
196-
}
197-
}
198-
if (taskInGroup == NUM_TASKS_IN_GROUP - 1) {
199-
iterationData[taskGroupNum] = BackgroundTaskMemoryData(getAppMemoryConsumption())
200-
continuation.resume(Unit)
169+
coroutinesScope.launch {
170+
MyLogger.d("Coroutine launched; iteration: $iterationNum; task $taskNum")
171+
iterationData[taskNum] = BackgroundTaskMemoryData(getAppMemoryConsumption())
172+
continuation.resume(Unit)
173+
try {
174+
awaitFlow.collect {
175+
throw CancellationException()
201176
}
202-
try {
203-
awaitFlow.collect {
204-
throw CancellationException()
205-
}
206-
} catch (e: CancellationException) {
207-
//no-op
208-
}
209-
MyLogger.d("Coroutine terminates; iteration: $iterationNum; group $taskGroupNum; task $taskInGroup")
177+
} catch (e: CancellationException) {
178+
//no-op
210179
}
180+
MyLogger.d("Coroutine terminates; iteration: $iterationNum; task $taskNum")
211181
}
212182
}
213183
}
@@ -224,10 +194,10 @@ class BackgroundTasksMemoryBenchmarkUseCase @Inject constructor(
224194
)
225195

226196
MyLogger.i("benchmarkThreadPool(); iteration: $iterationNum")
227-
val threadsBarrier = CyclicBarrier(NUM_TASK_GROUPS * NUM_TASKS_IN_GROUP + 1)
228-
for (taskGroupNum in 0 until NUM_TASK_GROUPS) {
197+
val threadsBarrier = CyclicBarrier(NUM_TASKS + 1)
198+
for (taskNum in 0 until NUM_TASKS) {
229199
coroutineContext.ensureActive()
230-
benchmarkSingleThreadPoolGroup(iterationNum, taskGroupNum, iterationData, threadPool, threadsBarrier)
200+
benchmarkSingleThreadPoolThread(iterationNum, taskNum, iterationData, threadPool, threadsBarrier)
231201
}
232202
threadsBarrier.await()
233203

@@ -240,26 +210,20 @@ class BackgroundTasksMemoryBenchmarkUseCase @Inject constructor(
240210
return !restartAppForNextIteration(BackgroundTasksMemoryBenchmarkPhase.THREAD_POOL, iterationNum)
241211
}
242212

243-
private suspend fun benchmarkSingleThreadPoolGroup(
213+
private suspend fun benchmarkSingleThreadPoolThread(
244214
iterationNum: Int,
245-
taskGroupNum: Int,
215+
taskNum: Int,
246216
iterationData:MutableMap<Int, BackgroundTaskMemoryData>,
247217
threadPool: ThreadPoolExecutor,
248218
threadsBarrier: CyclicBarrier
249219
) {
250220
suspendCoroutine { continuation ->
251-
val groupThreadBarrier = CyclicBarrier(NUM_TASKS_IN_GROUP)
252-
for (taskInGroup in 0 until NUM_TASKS_IN_GROUP) {
253-
threadPool.execute {
254-
MyLogger.d("Thread pool started; iteration: $iterationNum; group $taskGroupNum; task $taskInGroup")
255-
groupThreadBarrier.await() // make sure all threads started
256-
if (taskInGroup == NUM_TASKS_IN_GROUP - 1) {
257-
iterationData[taskGroupNum] = BackgroundTaskMemoryData(getAppMemoryConsumption())
258-
continuation.resume(Unit)
259-
}
260-
threadsBarrier.await()
261-
MyLogger.d("Thread pool terminates; iteration: $iterationNum; group $taskGroupNum; task $taskInGroup")
262-
}
221+
threadPool.execute {
222+
MyLogger.d("Thread pool started; iteration: $iterationNum; task $taskNum")
223+
iterationData[taskNum] = BackgroundTaskMemoryData(getAppMemoryConsumption())
224+
continuation.resume(Unit)
225+
threadsBarrier.await()
226+
MyLogger.d("Thread pool terminates; iteration: $iterationNum; task $taskNum")
263227
}
264228
}
265229
}
@@ -311,9 +275,8 @@ class BackgroundTasksMemoryBenchmarkUseCase @Inject constructor(
311275
}
312276

313277
companion object {
314-
const val NUM_TASK_GROUPS = 50
315-
const val NUM_TASKS_IN_GROUP = 1
316-
const val NUM_ITERATIONS = 10
278+
const val NUM_TASKS = 100
279+
const val NUM_ITERATIONS = 3
317280

318281
const val LABEL_THREADS = "threads"
319282
const val LABEL_COROUTINES = "coroutines"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.techyourchance.android.backgroundtasksbenchmark.memory
2+
3+
import com.techyourchance.android.common.maths.LinearFitCoefficients
4+
5+
data class BackgroundTasksMemoryResult(
6+
val averageAppMemoryConsumption: Map<Int, BackgroundTaskMemoryData>,
7+
val averageLinearFitCoefficients: LinearFitCoefficients,
8+
val tasksData: Map<Int, Map<Int, BackgroundTaskMemoryData>>,
9+
) {
10+
companion object {
11+
val NULL_OBJECT = BackgroundTasksMemoryResult(mapOf(), LinearFitCoefficients(0.0, 0.0), mapOf())
12+
}
13+
}
Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,69 @@
11
package com.techyourchance.android.backgroundtasksbenchmark.memory
22

3+
import com.techyourchance.android.common.maths.LinearFitCalculator
4+
import com.techyourchance.android.common.maths.LinearFitCoefficients
35
import com.techyourchance.android.database.entities.backgroundtasksmemory.BackgroundTasksMemoryDao
6+
import com.techyourchance.android.database.entities.backgroundtasksmemory.BackgroundTasksMemoryDb
47
import java.util.concurrent.ConcurrentHashMap
58
import javax.inject.Inject
69

710
class FetchBackgroundTaskMemoryDataUseCase @Inject constructor(
811
private val backgroundTasksMemoryDao: BackgroundTasksMemoryDao,
9-
12+
private val linearFitCalculator: LinearFitCalculator,
1013
) {
1114

12-
suspend fun fetchData(label: String): BackgroundTaskGroupsMemoryResult {
15+
suspend fun fetchData(label: String): BackgroundTasksMemoryResult {
1316
val dbEntities = backgroundTasksMemoryDao.fetchAllWithLabel(label)
14-
val sortedDbEntities = dbEntities.sortedBy { it.iteration * 100 + it.tasksGroup }
15-
val tasksData = preAllocatedDataStructures()
17+
val sortedDbEntities = dbEntities.sortedBy { it.iterationNum * 100 + it.taskNum }
18+
val tasksData = preAllocatedDataStructures(sortedDbEntities)
1619
sortedDbEntities.forEach { dbEntity ->
17-
tasksData[dbEntity.iteration]!![dbEntity.tasksGroup] = BackgroundTaskMemoryData(
20+
tasksData[dbEntity.iterationNum]!![dbEntity.taskNum] = BackgroundTaskMemoryData(
1821
dbEntity.memoryInfo.consumedMemory
1922
)
2023
}
21-
val averagedTasksData = computeAverage(tasksData)
22-
val (averageLinearFitSlope, averageLinearFitIntercept) = computeLinearFitCoefficients(averagedTasksData)
23-
return BackgroundTaskGroupsMemoryResult(
24+
val averagedTasksData = computeAverageOfIterations(tasksData)
25+
val averageLinearFitCoefficients = calculateLinearFit(averagedTasksData)
26+
return BackgroundTasksMemoryResult(
2427
averagedTasksData,
25-
averageLinearFitSlope,
26-
averageLinearFitIntercept,
28+
averageLinearFitCoefficients,
2729
tasksData,
2830
)
2931
}
3032

31-
private fun preAllocatedDataStructures(): MutableMap<Int, MutableMap<Int, BackgroundTaskMemoryData>> {
32-
val data = ConcurrentHashMap<Int, MutableMap<Int, BackgroundTaskMemoryData>>(
33-
BackgroundTasksMemoryBenchmarkUseCase.NUM_ITERATIONS
34-
)
35-
for (iterationNum in 0 until BackgroundTasksMemoryBenchmarkUseCase.NUM_ITERATIONS) {
36-
val iterationData = ConcurrentHashMap<Int, BackgroundTaskMemoryData>(BackgroundTasksMemoryBenchmarkUseCase.NUM_TASK_GROUPS)
37-
for (taskNum in 0 until BackgroundTasksMemoryBenchmarkUseCase.NUM_TASK_GROUPS) {
33+
private fun preAllocatedDataStructures(sortedDbEntities: List<BackgroundTasksMemoryDb>): MutableMap<Int, MutableMap<Int, BackgroundTaskMemoryData>> {
34+
val numIterations = sortedDbEntities.maxOf { it.iterationNum + 1 }
35+
val numTasks = sortedDbEntities.maxOf { it.taskNum + 1}
36+
37+
val data = ConcurrentHashMap<Int, MutableMap<Int, BackgroundTaskMemoryData>>(numIterations)
38+
39+
for (iterationNum in 0 until numIterations) {
40+
val iterationData = ConcurrentHashMap<Int, BackgroundTaskMemoryData>(numTasks)
41+
for (taskNum in 0 until numTasks) {
3842
iterationData[taskNum] = BackgroundTaskMemoryData.NULL_OBJECT
3943
}
4044
data[iterationNum] = iterationData
4145
}
4246
return data
4347
}
4448

45-
private fun computeAverage(tasksData: MutableMap<Int, MutableMap<Int, BackgroundTaskMemoryData>>): Map<Int, BackgroundTaskMemoryData> {
46-
val numTaskGroups = tasksData[0]!!.size
47-
val averageTasksMemoryConsumptions = ConcurrentHashMap<Int, BackgroundTaskMemoryData>(
48-
numTaskGroups
49-
)
50-
for (taskNum in 0 until numTaskGroups) {
49+
private fun computeAverageOfIterations(tasksData: MutableMap<Int, MutableMap<Int, BackgroundTaskMemoryData>>): Map<Int, BackgroundTaskMemoryData> {
50+
val numIterations = tasksData.size
51+
val numTasks = tasksData[0]!!.size
52+
val averageTasksMemoryConsumptions = ConcurrentHashMap<Int, BackgroundTaskMemoryData>(numTasks)
53+
for (taskNum in 0 until numTasks) {
5154
var sumTaskMemoryConsumption = 0f
52-
for (iterationNum in 0 until tasksData.size) {
55+
for (iterationNum in 0 until numIterations) {
5356
sumTaskMemoryConsumption += tasksData[iterationNum]!![taskNum]!!.consumedMemory
5457
}
55-
val averageTaskMemoryConsumption = sumTaskMemoryConsumption / numTaskGroups
58+
val averageTaskMemoryConsumption = sumTaskMemoryConsumption / numIterations
5659
averageTasksMemoryConsumptions[taskNum] = BackgroundTaskMemoryData(averageTaskMemoryConsumption)
5760
}
5861
return averageTasksMemoryConsumptions
5962
}
6063

61-
private fun computeLinearFitCoefficients(tasksData: Map<Int, BackgroundTaskMemoryData>): Pair<Float, Float> {
62-
val n = tasksData.size
63-
var sumX = 0f
64-
var sumY = 0f
65-
var sumXY = 0f
66-
var sumXX = 0f
6764

68-
for (i in 0 until n) {
69-
sumX += i
70-
sumY += tasksData[i]!!.consumedMemory
71-
sumXY += i * tasksData[i]!!.consumedMemory
72-
sumXX += i * i
73-
}
74-
75-
val slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX)
76-
val intercept = (sumY - slope * sumX) / n
77-
78-
return Pair(slope, intercept)
65+
private fun calculateLinearFit(data: Map<Int, BackgroundTaskMemoryData>): LinearFitCoefficients {
66+
val inputData = data.map { entry -> Pair(entry.key.toDouble(), entry.value.consumedMemory.toDouble()) }
67+
return linearFitCalculator.calculateLinearFit(inputData)
7968
}
80-
81-
8269
}

0 commit comments

Comments
 (0)