sábado, 3 de março de 2012

C: Avaliação incompleta

Existem duas formas de avaliação em expressões lógicas, a avaliação completa e a incompleta.

Por definição, no Pascal a avaliação sempre é completa (O Turbo Pascal tinha uma opção para mudar este comportamento.), e no C é incompleta.

O que é isto? Quais são as consequências? Como melhor aproveitar isto.

No Pascal as avaliações de expressões lógicas são completas, i.e., todas as partes são avaliadas, não importando se no meio delas já é possível obter o resultado final ou não. No exemplo abaixo, e a foi maior ou igual a b - portanto a primeira comparação é falsa, o que implica que o resultado do and das duas expressões é falso - b ainda será comparado com c.

        if a < b and b < c then
        begin
                { ... }
        end;

O único meio (fora a opção mencionada acima no caso do Turbo Pascal) é separar as partes da expressão lógica em um if dentro do outro.

        if a < b then
                if b < c then
                begin
                        { ... }
                end;

No C a avaliação é incompleta, i.e., é feita o mínimo de operações necessárias para obter o resultado.

No exemplo equivalente abaixo, se a foi maior ou igual a b, a segunda comparação não será feita, pois já temos o resultado da expressão toda (No and basta um ser falso para o resultado final ser falso.). Mas se a for menor que b, então a segunda comparação será feita.

        if( ( a < b ) && ( b < c ) )
        {   
                /* ... */
        }

Então o código acima é igual ao código abaixo. (Na realidade, é o código abaixo que é gerado na compilação, mas só com conhecimento de Assembler para entender isto.)

        if( a < b )    
                if( b < c )
                {
                        /* ... */
                }

Como podemos usar a avaliação incompleta mais ainda a nosso favor?

Podemos colocar primeiro o que pode definir mais rapidamente o resultado final. Por exemplo, se tem mais chances de a ser maior ou igual a b - isto é, a primeira comparação ser falsa - do que a segunda comparação, então a ordem no exemplo acima está perfeita. Mas se tem mais chances de b ser maior ou igual a c - isto é, a segunda comparação ser falsa - do que a primeira comparação ser falsa, então é melhor mudar a ordem de avaliação das comparações, para um desempenho médio melhor. Serão menos vezes que a segunda comparação será feita.

Mas se a expressão for com or (||), e não and (&&), então devemos colocar em primeiro o que tem mais chances de ser verdadeiro.

        if( ( a < b ) || ( b < c ) )
        {   
                /* ... */
        }

Se a primeira comparação tem mais chances de ser verdadeira, então tem mais chances da segunda comparação não ser avaliada.

Assim podemos usar, quando temos possibilidades mais conhecidas, a avaliação incompleta mais a nosso favor ainda.

E se uma comparação tem um grande custo? Um exemplo é uma comparação que tem um custo alto, como um strcmp(), por exemplo. Olhe os dois casos abaixo:

        if( ( a < b ) && strcmp( c,d ) )
        {
                /* ... */
        }
 
        if( strcmp( c,d ) && ( a < b ) )
        {
                /* ... */
        }

Qual dos dois casos acima é o melhor? No primeiro caso, a strcmp() somente será executada se a primeira comparação for verdadeira, e no segundo ela sempre será executada. Mesmo que a comparação entre a e b quase sempre tenha resultado verdadeiro, o consumo de tempo de processamento da strcmp() é tão mais alto do que uma simples comparação que, umas poucas vezes que ela não é executada dê um ganho de tempo significativo no processamento total.

Então, para aproveitar bem a avaliação incompleta, pode-se levar em conta a probabilidade dos resultados intermediários das expressões lógicas, e o custo de processamento para obter estes resultados intermediários.

Nenhum comentário:

Postar um comentário