Eficiência String com Java e Groovy

Posted Almost 7 years ago. Visible to the public.

Existe uma diferença considerável na execução de concatenação/construção de Strings por diferentes métodos:

  1. Uso de StringBuilder e seus "append"s
  2. Uso de String.concat, como em x.concat(':').concat(y)
  3. Uso de leftShift, como em x << ":" << y
  4. Uso de List com lista.join('')
  5. Uso de GString, como em "$x:$y"
  6. Uso de soma de String's, como em x + ':' + y

Segue abaixo um "teste benchmark" que pode ser executado no GroovyConsole para visualizar a diferença de tempo.
Em minha máquina as principais saídas produzidas (para a média de 3 execuções de 1 milhão de repetições cada) foram:

| Teste 2 Strings | Média | Execuções Individuais
| StringBuilder | 298 ms | 334 ms / 283 ms / 279 ms
| String.concat | 271 ms | 281 ms / 266 ms / 268 ms
| leftShift | 327 ms | 340 ms / 323 ms / 320 ms
| List+join | 412 ms | 418 ms / 411 ms / 408 ms
| GString | 338 ms | 355 ms / 330 ms / 329 ms
| Soma String | 531 ms | 549 ms / 521 ms / 525 ms

| Teste 4 Strings | Média | Execuções Individuais
| StringBuilder | 409 ms | 511 ms / 362 ms / 354 ms
| String.concat | 422 ms | 447 ms / 416 ms / 404 ms
| leftShift | 566 ms | 603 ms / 549 ms / 547 ms
| List+join | 540 ms | 564 ms / 527 ms / 531 ms
| GString | 655 ms | 676 ms / 644 ms / 647 ms
| Soma String | 1158 ms | 1175 ms / 1151 ms / 1150 ms

| Teste 10 Strings | Média | Execuções Individuais
| StringBuilder | 578 ms | 580 ms / 576 ms / 578 ms
| String.concat | 961 ms | 943 ms / 942 ms / 998 ms
| leftShift | 1457 ms | 1473 ms / 1468 ms / 1431 ms
| List+join | 1561 ms | 1535 ms / 1603 ms / 1546 ms
| GString | 1902 ms | 1896 ms / 1898 ms / 1913 ms
| Soma String | 3495 ms | 3483 ms / 3501 ms / 3501 ms

Estes resultados comprovam a eficiência do StringBuilder e a estabilidade de seu tempo desde a concatenação de apenas 2 strings até 10 strings, imagine em casos onde se concatenam milhares de strings em uma lógica complexa? O tempo perdido pode se tornar expressivo. Principalmente ao compararmos com a Soma String que em geral costuma demorar o dobro do tempo quando comparado à qualquer outra alternativa, mas ainda é o método mais utilizado pelos programadores.

int qtdMaxStrings = 10
List<String> lista10Strings = (1..qtdMaxStrings).collect { it.toString().padLeft(7, '0') }
List<List<String>> testesStrings = []
(2..qtdMaxStrings).each { int qtd ->
    testesStrings.add(lista10Strings.subList(0, qtd))
}

testesStrings.each { List<String> testeStrings ->

    println "| **Teste ${testeStrings.size()} Strings** | **Média** | **Execuções Individuais**"

    executaTeste('StringBuilder', testeStrings) {
        StringBuilder stringBuilder = new StringBuilder(testeStrings.head())
        for (String umaString : testeStrings.tail()) {
            stringBuilder.append(':')
            stringBuilder.append(umaString)
        }
        String textoUnido = stringBuilder.toString()
        return textoUnido
    }

    executaTeste('String.concat', testeStrings) {
        String textoUnido = testeStrings.head()
        for (String umaString : testeStrings.tail()) {
            textoUnido = textoUnido.concat(':').concat(umaString)
        }
        return textoUnido
    }

    executaTeste('leftShift', testeStrings) {
        String textoUnido = testeStrings.head()
        for (String umaString : testeStrings.tail()) {
            textoUnido = textoUnido << ":" << umaString
        }
        return textoUnido
    }

    executaTeste('List<String>+join', testeStrings) {
        String textoUnido = testeStrings.join(':')
        return textoUnido
    }

    executaTeste('GString', testeStrings) {
        String textoUnido = testeStrings.head()
        for (String umaString : testeStrings.tail()) {
            textoUnido = "${textoUnido}:${umaString}"
        }
        return textoUnido
    }

    executaTeste('Soma String', testeStrings) {
        String textoUnido = testeStrings.head()
        for (String umaString : testeStrings.tail()) {
            textoUnido = textoUnido + ':' + umaString
        }
        return textoUnido
    }

    println ''
}

void executaTeste(String identificadorTeste, List<String> testeStrings, Closure execucao) {
    String textoUnidoEsperado = testeStrings.join(':')
    List<Long> duracoesMs = []
    int qtdAnalises = 3
    qtdAnalises.times {
        assert execucao.call() == textoUnidoEsperado

        long timeInicioNano = System.nanoTime()
        int loopAnaliseDeDesempenho = 1_000_000
        loopAnaliseDeDesempenho.times {
            execucao.call()
        }
        long timeFimNano = System.nanoTime()
        long duracaoMs = (long) (timeFimNano - timeInicioNano) / 1e6
        duracoesMs.add(duracaoMs)
    }
    long media = duracoesMs.sum() / qtdAnalises
    println "| ${identificadorTeste.padRight(19, ' ')} | ${media.toString().padLeft(6, ' ')} ms | ${duracoesMs.collect { "${it.toString().padLeft(5, ' ')} ms" }.join(' / ')}"
}

println 'FIM'
Bruno Vieira
Last edit
Almost 6 years ago
Bruno Vieira
Posted by Bruno Vieira to ZeroGlosa (2017-07-11 21:42)