Tem como otimizar?

Hoje estava desenvolvendo um sistema e uma velha duvida me veio a mente.

Vector v = new Vector();
// alguma coisa com v
for (int i = 0; i < v.size(); i++) {
   // alguma coisa aqui.
}

Já fui muito criticado por usar esse tipo de construção devido as constantes chamadas ao método size() como condição de um loop for. A minha pergunda é, o JAVAC ou a JVM não otimizam esse tipo de construção? tipo…

Vector v = new Vector();
// alguma coisa com v.
int s = v.size();
for (int i = 0; i < s; i++) {
   // alguma coisa aqui.
}

A JVM pode vir a otimizar a chamada do método de alguma forma, mas considerando que o size fica guardado lá em uma variável do Vector do mesmo jeito que você faz aí na sua implementação, não vai fazer muita diferença não.

Nessas situações você pode usar iterator:

http://java.sun.com/javase/6/docs/api/java/util/Vector.html

http://java.sun.com/javase/6/docs/api/java/util/List.html#iterator()

Otimizam se você usar -server como opção de execução, e não otimizam se você usar -client (que é o default).
Esquisito mas verdade. Eu vi isso em um blog de um cara da Sun que trabalha com a JVM.
Dica: use isto aqui, que é mais rápido.

List v = new ArrayList(); // dificilmente se justifica usar Vector - acho que só em código Java 1.1.
// alguma coisa com v.
for (int i = 0, s = v.size(); i < s; i++) {
   // alguma coisa aqui.
}
// ... ou (Java 5.0)
List<String> v = new ArrayList<String>();
for (String s : v) {
}

[quote=dev.rafael

Vector v = new Vector(); // alguma coisa com v. int s = v.size(); for (int i = 0; i < s; i++) { // alguma coisa aqui. } [/quote]

A JVM nunca fará isso. O motivo é simples: encapsulamento. A JVM não sabe o que os métodos fazem.
Algumas construções otimizarão esse codigo. Para começar espera-se que o método size() retorne uma variável
e não que vá perder tempo contando os itens. Ai, o custo é o de executar um método. Mas se o método for final
a JVM acabara resolvendo da uma forma semelhante à que v indicou

for (int i = 0; i < v!size; i++)

em que o ! representa aqui o acesso direto à variável que o método size() retorna (supondo que retorna).

Agora, o problema de otimização neste nivel é da responsabildiade da JVM e vc como programador não pode, nem deve, assumir que essa otimização existe.

O problema de usar um inteiro para iterar uma lista é que essa nem sempre é a forma mais rápida de iterar.
A sua sorte é que Vector implementa RandomAccess , que básicamente significa que é mais rápido iterar usando get(i) do que iterator.next() , mas principalmente significa que é mais rápido fazer get(i) do que no caso normal.
Enfim, se o seu exemplo fosse iterar um List genérico a otimização seria usar o iterador , ou verifica se a lista implementa RandomAccess.
Este tipo de otimização “alto nivel” é que o programador deve cuidar e não se chamar size() várias vezes é ruim ou não.

Claro que, em caso de muita necessidade vc pode sempre usar um profiler.

Detalhe: quando vc usa uma comparação dessa forma

i > 100

ela é mais lenta do que comparar com 0 ou 1. A explicação é que para comparar com valores triviais (0, 1 e talvez mais alguma coisa) vc tem instruções específicas, enquanto que para um numero diferente vc gasta com uma instrução que busca esse número em outro lugar.

Se vc buscar em uma variavel, a queda de performance pode ser imperceptivel. Por outro lado, não é interessante vc sacrificar a legibilidade de um programa em troca de alguns milissegundos a menos que isso seja crucial (como uma aplicação embarcada).

[quote=dev.rafael]Hoje estava desenvolvendo um sistema e uma velha duvida me veio a mente.

Vector v = new Vector(); // alguma coisa com v. int s = v.size(); for (int i = 0; i < s; i++) { // alguma coisa aqui. } [/quote]

Esse seu código certamente produzirá efeito de tempo melhor, simplesmente porque o acesso ao método size de vector é synchronized, e a variável interna é elementCount e não size.

O que realmente valeria a pena você fazer, seria substituir Vector por ArrayList.

Mas ficando no foco do seu código:

int s = v.size(); for (int i = 0; i < s; i++) {

é melhor que :

for (int i = 0; i < v.size(); i++) {   

principalmente por ser um método synchronized.

Otimização hardcore que seria possível, mas depende muito se a ordem de iteração não seja importante para o seu código, como em casos de somatórios, é utilizar a ordem inversa e a condição de finalização ser algo do tipo i>0 ou i!=0.

fw

[quote=thingol]Otimizam se você usar -server como opção de execução, e não otimizam se você usar -client (que é o default).
Esquisito mas verdade. Eu vi isso em um blog de um cara da Sun que trabalha com a JVM.
Dica: use isto aqui, que é mais rápido.

List v = new ArrayList(); // dificilmente se justifica usar Vector - acho que só em código Java 1.1. // alguma coisa com v. for (int i = 0, s = v.size(); i < s; i++) { // alguma coisa aqui. } // ... ou (Java 5.0) List<String> v = new ArrayList<String>(); for (String s : v) { } [/quote]

o foreach otimiza thingol?

Acho que a grande questão aqui é.

Para que otimizar algo que certamente não é o gargalo da sua aplicação?

Digamos que o seu for leve de 0.05 segundos 0.03 segundos. Será que seu usuário vai perceber a diferença de 0.02 segundos?

Otimização deve ser feita em etapas:

  1. Planejamento de macro estruturas desde o momento do projeto (queries, organização de classes, uso das classes corretas, são exemplos disso);
  2. Uso de algoritmos reconhecidamente eficientes (iterator ao invés do que você fez, Collections.sort antes de se aventurar num bubble sort, StringBuilder no lugar de concatenação de Strings, etc).
  3. Uso de profiler para otimização de micro-código, se esse for o problema.

Otimizações de microcódigo como essa tem pouco ou nenhum impacto sobre a performance final do sistema. Mas tem grande impacto sobre a legibilidade, além de tornar a manutenção cada vez mais complicada.

[quote=LPJava][quote=thingol]Otimizam se você usar -server como opção de execução, e não otimizam se você usar -client (que é o default).
Esquisito mas verdade. Eu vi isso em um blog de um cara da Sun que trabalha com a JVM.
Dica: use isto aqui, que é mais rápido.

List v = new ArrayList(); // dificilmente se justifica usar Vector - acho que só em código Java 1.1. // alguma coisa com v. for (int i = 0, s = v.size(); i < s; i++) { // alguma coisa aqui. } // ... ou (Java 5.0) List<String> v = new ArrayList<String>(); for (String s : v) { } [/quote]

o foreach otimiza thingol?[/quote]

Não. O compilador transforma o foreach em um dos seguintes códigos:

  • Se a classe do objeto implementar Iterable, então ele transforma
for (String s : v) {
}

em

for (Iterator <String> it = v.iterator(); it.hasNext(); ) {
    String s = it.next();
}
  • Se o objeto for um array, a transformação é:
for (int i = 0; i < v.length; ++i) {
    String s = v[i];
}

humm saquei… so otimiza as linhas de programacao :smiley:

Dentro da medida do possível, use o foreach em vez de ficar usando explicitamente iterators ou índices.

Cumpre notar que nem sempre o problema se resolve só com foreach - muitas vezes você precisa mesmo do índice, ou você tem de alterar elementos da coleção. Um exemplo é este:

double[] quadrados = new double[100];
for (int i = 0; i < quadrados.length; ++i) {
    quadrados[i] = i * i;
}

Este problema não se resolve com foreach porque você está atribuindo um novo valor ao elemento da coleção. Não dá para fazer o seguinte:

// parece que funciona, mas está errado.
double[] quadrados = new double[100];
int i = 0;
for (double quadrado : quadrados) {
    quadrado = i * i;
    i = i + 1;
}

porque o equivalente ao código errado é:

double[] quadrados = new double[100];
int i = 0;
for (int j = 0; j < quadrados.length; ++j) {
    double quadrado = quadrados[j];
    quadrado = i * i;
    i = i + 1;
}

ou seja, não há nenhuma atribuição a quadrados[j].

Também não dá para resolver com foreach as iterações onde você precisa eliminar elementos da lista que você está iterando.

Um exemplo é esse:

String nomeParaRemover = "Vinicius";
List<String> umaLista = new ArrayList<String>(
   Arrays.asList("João", "Maria", "Vinícius", "José"));

for (String nome : umaLista) {
   if (nome.equals(nomeParaRemover)) {
      umaLista.remove(nome);  //Vai gerar ConcurrentModificationException
   } 
}

O correto é remover através do iterator, como mostrado abaixo:

[code]
String nomeParaRemover = “Vinicius”;
List umaLista = new ArrayList(
Arrays.asList(“João”, “Maria”, “Vinícius”, “José”));

Iterator it = umaLista.iterator();
while (it.hasNext()) {
String nome = it.next();
if (nome.equals(nomeParaRemover)) {
it.remove();
}
}[/code]

Uma alternativa um pouco menos eficiente, mas bastante prática e que gera um código bastante limpo é fazer uma cópia da lista e iterar sobre a cópia.

[code]
String nomeParaRemover = “Vinicius”;
List umaLista = new ArrayList(
Arrays.asList(“João”, “Maria”, “Vinícius”, “José”));
List copia = new ArrayList(umaLista);

for (String nome : copia) { //Percorremos a cópia
if (nome.equals(nomeParaRemover)) {
umaLista.remove(nome); //Mas removemos da lista original
}
}[/code]

Outra alternativa, é percorrer a lista ao contrário. Mas isso só serve para o ArrayList:

[code]
String nomeParaRemover = “Vinicius”;
List umaLista = new ArrayList(
Arrays.asList(“João”, “Maria”, “Vinícius”, “José”));

for (int i = umaLista.size()-1; i >= 0; i–) { //Percorremos ao contrário
if (umaLista.get(i).equals(nomeParaRemover)) {
umaLista.remove(i); //Pois esse comando altera os índices de quem vem depois.
}
}[/code]

Embora gerem um código limpo, nenhuma dessas técnicas é mais eficiente ou mais genérica do que usar o próprio iterator, quando o assunto é remoção.

po… saquei… valeu ai pelas dicas… a ideia do foreach é qdo serve para todos os elementos… do conjunto… se fosse alterar todos os elementos… do conjunto poderia usar foreach… mas se for especifico… entao… caimos no for tradicional.

valeu vinny e thingol :smiley:

flw!