Kord-Extensions/kord-extensions

Optimize String Concatenation

Closed this issue · 1 comments

Description

In several place of modules, the concatenation of string can decrease the performance.
Currently, the concatenation is make by the operator +=. This is not a problem for the inline concatenation "a" + "b" + "c",
but when it's not, the compiler creates several StringBuilder instance.

So, below, the explanation sent in discord server :

When you add two string, this is the compiled result :

val a = "a"
val b = "b"
val c = "c"
println(a + b + c) // StringBuilder(a).append(b).append(c).toString()

So, here you can see, there is no issue about the performance, because an inline concatenation of string create an once StringBuilder

But, when it's not inline, for each concatenation, a new StringBuilder will be create

val a = "a"
a += "b" // a = StringBuilder(a).append("b").toString()
a += "c" // a = StringBuilder(a).append("c").toString()
println(a)

Or if there is loop, condition, it's the same case

var a = "a"
if(Random.nextInt() == 2) {
   a += "b" // a = StringBuilder(a).append("b").toString()
}
a += "c" // a = StringBuilder(a).append("c").toString()
listOf(1, 2 ,3).forEach {
    a += it.toString() // a = StringBuilder(a).append(it).toString() (x3)
}

println(a)

In the code i seen this for example

list.joinToString { // StringBuilder create for the result
var x = "..;"

if(..) {
  x += "..." // new StringBuilder created
}
// etc.
x
}

A joinToString use an instance of StringBuilder, to concat without performance issue the result of each iteration
BUT, if you provoke the creation of a new StringBuilder in the iteration, so you loss all advantage of joinToString and StringBuilder

Code location

I post here some (not all) part of code concerned by this issue

annotation-processor-1
annotation-processor-2

extra-modules

kord-extension-1
kord-extension-2

etc.

To find all, i advice to use the option in IntelliJ Find in Files with the value += "

Solution

There is two choices to resolve that.

  1. Use the method buildString { }
    The method buildString allows to have a StringBuilder as a receiver variable
println(buildString {
 append("a")
 append("b")
 // etc.
})
  1. Use directly a new instance of StringBuilder
    Same than first solution, but you need to write the variable
val builder = StringBuilder()
builder .append("a")
builder .append("b")
 // etc.
println(builder.toString())
})

For the list and the method joinToString, if you concat in the joinToString, prefer use a StringBuilder and a forEach instead

It's done - completely destroyed any usage of += with strings in KordEx.