Comparação (não totalmente justa) entre Java e C

Eis um dos motivos pelo qual detesto Java… Em C eu posso concatenar duas constantes definidas como símbolos do pré-compilador que o compilador as armazenará como uma string única e, simplesmente, passará o ponteiro para uma função puts() para imprimí-la… Em Java não há jeito de definir símbolos que só serão conhecidos pelo analizador léxico da linguagem, você tem que declará-las em classes de armazenamento, de preferẽncia com o modificador “final”.

Eis um exemplo em C:

#include <stdio.h>

#define STR1 "a"
#define STR2 "b"

void main(int argc, char *argv[])
{
  puts(STR1 STR2);
}

Note que STR1 e STR2 podem ser concatenadas sem o auxílio de qualquer operador. A string que o compilador verá é “ab”. Observando o código gerado pelo compilador, temos:

.section .rodata
.LC0:
.string "ab"

.section .text
.globl main
.type main,@function
main:
  mov rdi,offset .LC0
  jmp puts

Ou seja, o compilador coloca a constante “ab” no segmento “.rodata”, obtém o endereço do primeiro byte da string e coloca-o em RDI e pula para puts. Nada é mais rápido que isso!

Agora, vejamos o mesmo código em Java:

public class Test {
  // As "constantes" precisam ser definidas como 
  // campos privados e finais! Não há como definir 
  // "símbolos" léxicos!
  private static final String STR1 = "a";
  private static final String STR2 = "b";

  public static void main(String[] args) {
    // É necessário usar a operação de concatenação de strings '+'!
    System.out.println(STR1 + STR2);
  }
}

Compile com:

$ javac Test.java

Para ver o que o javac fez, podemos usar o utilitário javap:

$ javap -l -c -verbose -constants Test.class > Test.javap

Eis o resultado (retirei a maioria das informações para não ficar muito grande):

public class Test
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#21         //  java/lang/Object."":()V
   #2 = Fieldref           #22.#23        //  java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #24            //  "ab"
   #4 = Methodref          #25.#26        //  java/io/PrintStream.println:(Ljava/lang/String;)V
   ...
   #7 = Utf8               STR1
   #8 = Utf8               Ljava/lang/String;
   #9 = Utf8               ConstantValue
  #10 = String             #29            //  "a"
  #11 = Utf8               STR2
  #12 = String             #30            //  "b"
  ...
{
  ...

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2             // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3             // String "ab"
         5: invokevirtual #4             // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return        
}

A função estática main obtém uma referência (ponteiro) para o membro java.io.PrintStream do objeto java.lang.System.out. e o empilha. Logo depois ele pega a referência ao objeto java.lang.String (que, o compilador foi esperto o suficiente para concatená-los!) e a empilha. Então ele “invoca” a função membro println da referência previamente empilhada…

A comparação pode parecer injusta, afinal, o código em C usou a string como um valor literal passado para puts(). Faça o mesmo, em Java, e você obterá o mesmíssimo código!!!

Um detalhe importante: A “máquina virtual” do Java (JVM) não possui o conceito de “registradores”. Tudo tem que ser empilhado e desempilhado. A mesma coisa acontece na instrução invokevirtual, que empilha a referência à função membro e depois faz o que tem que fazer… Fica claro, pra você, que esse treco é, pelo menos, umas 10 vezes mais lento do que o equivalente em C?

Tudo muito bonito e certinho, mas por que diabos as constantes STR1 e STR2 continuam no código final? (Note as referências #10 e #12). A explicação é a seguinte: Java não tem como saber para que diabos essa classe vai ser usada… Mesmo que você defina uma função main() estática, em conformidade com a especificação de applets executáveis, a classe pode ser usada para outro fim!! Então, Java mantém tudo quanto é lixo desnecessário dentro do código final.

A única otimização possível (e apenas neste caso!) foi a concatenação prévia de STR1 e STR2. E isso ocorreu somente porque as defini como final, no corpo da classe. Experimente retirar o final e você verá algo assim:

public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuilder
         6: dup           
         7: invokespecial #4                  // Method java/lang/StringBuilder."":()V
        10: getstatic     #5                  // Field STR1:Ljava/lang/String;
        13: invokevirtual #6                  // Method java/lang/StringBuilder.append:(...);
        16: getstatic     #7                  // Field STR2:Ljava/lang/String;
        19: invokevirtual #6                  // Method java/lang/StringBuilder.append:(...);
        22: invokevirtual #8                  // Method java/lang/StringBuilder.toString:();
        25: invokevirtual #9                  // Method java/io/PrintStream.println:(String;)V
        28: return

Isso ai é equivalente a:

public static void main(String[] args) {
  StringBuilder sb = new StringBuilder;
  sb.append(STR1);
  sb.append(STR2);
  String str = sb.ToString();
  System.out.println(str);
}

Yep! As strings serão concatenadas separadamente e, para isso, um objeto da classe StringBuilder precisa ser criado no heap! Mesmo que os campos continuem como “private” e não há como acessá-los de fora da função main… Note: Ainda temos apenas duas constantes, strings, que só são usadas em main() e o compilador simplesmente ignora esse fato, criando código inchado (bloated!).

E olha que esse é um exemplo bem simples! Imagina só aquelas aplicações web com trocentas mil classes, usando MVC e sabe-lá-o-que-mais?!

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s