-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2456 from DataDog/tvaleev/rum-6286/join-to-string…
…-optimizations RUM-6286: replacing `joinToString` when it possible, refactor a bit
- Loading branch information
Showing
12 changed files
with
241 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
.../src/test/kotlin/com/datadog/android/utils/JointToStringVsStringBuilderPerformanceTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/* | ||
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. | ||
* This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
* Copyright 2016-Present Datadog, Inc. | ||
*/ | ||
package com.datadog.android.utils | ||
|
||
import com.datadog.android.internal.utils.appendIfNotEmpty | ||
import fr.xgouchet.elmyr.Forge | ||
import fr.xgouchet.elmyr.junit5.ForgeExtension | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.extension.ExtendWith | ||
import org.junit.jupiter.api.extension.Extensions | ||
import kotlin.math.pow | ||
import kotlin.math.round | ||
import kotlin.math.sqrt | ||
import kotlin.system.measureNanoTime | ||
|
||
@Extensions( | ||
ExtendWith(ForgeExtension::class) | ||
) | ||
internal class JointToStringVsStringBuilderPerformanceTest { | ||
|
||
@Test | ||
fun `M be faster than joinToString W buildString`(forge: Forge) { | ||
val itemsForJoin = forge.aList(ITEMS_TO_JOIN) { forge.aString() } | ||
val joinToStringExecutionTime = mutableListOf<Long>() | ||
val buildStringExecutionTime = mutableListOf<Long>() | ||
|
||
var jointToStringResult: String | ||
var builderResult: String | ||
|
||
repeat(REPETITION_COUNT) { | ||
joinToStringExecutionTime.add( | ||
measureNanoTime { | ||
val jointToStringContainer = mutableListOf<String>() | ||
for (item in itemsForJoin) { | ||
jointToStringContainer.add(item) | ||
} | ||
jointToStringResult = jointToStringContainer.joinToString(separator = " ") { it } | ||
} | ||
) | ||
|
||
buildStringExecutionTime.add( | ||
measureNanoTime { | ||
builderResult = buildString { | ||
itemsForJoin.forEach { item -> appendIfNotEmpty(' ').append(item) } | ||
} | ||
} | ||
) | ||
|
||
assertThat(builderResult).isEqualTo(jointToStringResult) // same result | ||
} | ||
|
||
val statisticsReport = ( | ||
"buildString:\n" + | ||
" mean = ${buildStringExecutionTime.mean}\n" + | ||
" std = ${buildStringExecutionTime.std}\n" + | ||
" cv = ${"%.2f".format(buildStringExecutionTime.cv)}%\n" + | ||
" p50 = ${buildStringExecutionTime.percentile(50)}\n" + | ||
" p90 = ${buildStringExecutionTime.percentile(90)}\n" + | ||
" p95 = ${buildStringExecutionTime.percentile(95)}\n" + | ||
" p99 = ${buildStringExecutionTime.percentile(99)}\n" + | ||
"\n" + | ||
"joinToString:\n" + | ||
" mean = ${joinToStringExecutionTime.mean}\n" + | ||
" std = ${joinToStringExecutionTime.std}\n" + | ||
" cv = ${"%.2f".format(joinToStringExecutionTime.cv)}%\n" + | ||
" p50 = ${joinToStringExecutionTime.percentile(50)},\n" + | ||
" p90 = ${joinToStringExecutionTime.percentile(90)},\n" + | ||
" p95 = ${joinToStringExecutionTime.percentile(95)},\n" + | ||
" p99 = ${joinToStringExecutionTime.percentile(99)}\n" | ||
) | ||
|
||
assertThat( | ||
buildStringExecutionTime.percentile(90) | ||
).withFailMessage( | ||
statisticsReport | ||
).isLessThan( | ||
joinToStringExecutionTime.percentile(90) | ||
) | ||
} | ||
|
||
companion object { | ||
private const val ITEMS_TO_JOIN = 10_000 | ||
private const val REPETITION_COUNT = 10_000 | ||
|
||
private val List<Long>.mean | ||
get() = (sum().toDouble() / size) | ||
|
||
private val List<Long>.std: Double | ||
get() { | ||
val m = mean | ||
return sqrt( | ||
sumOf { (it - m).pow(2.0) } / size | ||
) | ||
} | ||
|
||
private val List<Long>.cv: Double | ||
get() = std / mean * 100.0 | ||
|
||
private fun List<Long>.percentile(k: Int): Long { | ||
val p = (k / 100.0) * (size + 1) | ||
return sorted()[round(p).toInt()] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
dd-sdk-android-internal/src/main/java/com/datadog/android/internal/utils/StringBuilderExt.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. | ||
* This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
* Copyright 2016-Present Datadog, Inc. | ||
*/ | ||
|
||
package com.datadog.android.internal.utils | ||
|
||
/** | ||
* This utility function helps to replace [joinToString] calls with more efficient [StringBuilder] methods calls. | ||
* In case when content should be separated with some separator, it's handy to add it in front of a new string only | ||
* if buffer already contains some data. | ||
* @param str string that should be added to the buffer only if it already contains some data. | ||
*/ | ||
fun StringBuilder.appendIfNotEmpty(str: String) = apply { | ||
if (isNotEmpty()) append(str) | ||
} | ||
|
||
/** | ||
* This utility function helps to replace [joinToString] calls with more efficient [StringBuilder] methods calls. | ||
* In case when content should be separated with some separator, it's handy to add it in front of a new string only | ||
* if buffer already contains some data. | ||
* @param char char that should be added to the buffer only if it already contains some data. | ||
*/ | ||
fun StringBuilder.appendIfNotEmpty(char: Char) = apply { | ||
if (isNotEmpty()) append(char) | ||
} |
73 changes: 73 additions & 0 deletions
73
dd-sdk-android-internal/src/test/java/com/datadog/internal/utils/StringBuilderExtKtTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. | ||
* This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
* Copyright 2016-Present Datadog, Inc. | ||
*/ | ||
|
||
package com.datadog.internal.utils | ||
|
||
import com.datadog.android.internal.utils.appendIfNotEmpty | ||
import fr.xgouchet.elmyr.annotation.StringForgery | ||
import fr.xgouchet.elmyr.junit5.ForgeExtension | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.extension.ExtendWith | ||
import org.junit.jupiter.api.extension.Extensions | ||
|
||
@Extensions( | ||
ExtendWith(ForgeExtension::class) | ||
) | ||
internal class StringBuilderExtKtTest { | ||
|
||
@Test | ||
fun `M add char W appendIfNotEmpty {buffer is not empty}`( | ||
@StringForgery(regex = ".+") initialContent: String | ||
) { | ||
// Given | ||
val buffer = StringBuilder(initialContent) | ||
|
||
// When | ||
buffer.appendIfNotEmpty(' ') | ||
|
||
// Then | ||
assertThat(buffer.toString()).isEqualTo("$initialContent ") | ||
} | ||
|
||
@Test | ||
fun `M add str W appendIfNotEmpty {buffer is not empty}`( | ||
@StringForgery(regex = ".+") initialContent: String | ||
) { | ||
// Given | ||
val buffer = StringBuilder(initialContent) | ||
|
||
// When | ||
buffer.appendIfNotEmpty(" ") | ||
|
||
// Then | ||
assertThat(buffer.toString()).isEqualTo("$initialContent ") | ||
} | ||
|
||
@Test | ||
fun `M not add any char W appendIfNotEmpty {buffer is empty}`() { | ||
// Given | ||
val buffer = StringBuilder() | ||
|
||
// When | ||
buffer.appendIfNotEmpty(' ') | ||
|
||
// Then | ||
assertThat(buffer.toString()).isEqualTo("") | ||
} | ||
|
||
@Test | ||
fun `M not add any str W appendIfNotEmpty {buffer is empty}`() { | ||
// Given | ||
val buffer = StringBuilder() | ||
|
||
// When | ||
buffer.appendIfNotEmpty(" ") | ||
|
||
// Then | ||
assertThat(buffer.toString()).isEqualTo("") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters