123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- <!--
- Introdução à Programação - 2017 - Prof. Leoônidas de Oliveira Brandão
- Introdução à eficiência de algoritmos
- LInE (Laboratory of Informatics in Education) - http://www.usp.br/line
- IME - USP
- Material didático
- Pode usar livrevemente este material para fins não comerciais, devendo sempre fazer referência à autoria.
- Sugestões/apontamento são bem vindos: leo@ime.usp.br (favor indicar no assunto "material de introducao 'a programacao")
- Prof. Leônidas de Oliveira Brandão
- http://www.ime.usp.br/~leo
- http://line.ime.usp.br
- http://www.matemtica.br
- -->
- <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
- <meta name='keywords' content='mac0122, material, professores, leonidas de oliveira brandao'>
- <link rel='stylesheet' type='text/css' href='css_img_js_conf/all.css'>
- <link rel='stylesheet' type='text/css' href='css_img_js_conf/line_introducao_programacao.css'>
- <script src="css_img_js_conf/defineLInE.js"></script> <!-- para referencias 'a documentos internos -->
- <div class="pagina">
-
- <p class="secao">Introdução à eficiência de algoritmos</p>
- <center><p>[
- <a href="#precisao" title="Problema de precisao numerica">Precisão</a> |
- <a href="#tempo" title="Eficiencia em relacao ao tempo de execucao">Velocidade</a> |
- <a href="#cosseno" title="Eficiencia: examinando a funcao transcendente <i>cosseno</i>">Cosseno</a> |
- <a href="#muitoingenuo" title="Uma primeira solucao nada eficiente para a funcao <i>cosseno</i>">Solução ineficiente</a> |
- <a href="#ingenuo" title="Uma segunda solucao para a funcao <i>cosseno</i>: ainda ineficaz">Solução</a> |
- <a href="#eficiente" title="Uma solucao eficiente para a funcao <i>cosseno</i>">Solução eficiente</a>
- ]</p>
- </center>
- <!--
- <p>
- <b>Alterações</b>: //2017 <i>C</i> e <i>Python</i>)
- </p>
- -->
- <p>
- Nesta seção examinaremos uma questão essencial à computação, se o algoritmo é ou não eficiente para resolver o problema de interesse.
- Sobre isso existem vários aspectos que podem ser considerados, aqui examinaremos dois: a <b>precisão</b> e a <b>velocidade</b>.
- <center>
- <img src="img/img_precisao_x_velocidade.png" title="Gráfico ilustrando que geralmente precisão varia inversamente com a velocidade"/>
- <br/>
- <i>Fig. 1. Representação de que maior velocidade de execução geralmente implica em menor precisão numérica.</i>
- </center>
- </p>
- <p>
- A <b style="color:#0000aa;">precisão</b> diz respeito à "qualidade" da resposta, é necessário que ela seja
- <b style="color:#00aa00;">suficientemente próxima da real solução</b> para o problema considerado.
- Para ilustrar podemos considerar um exemplo <i>numérico</i>, por exemplo, tentar encontrar a raíz para determinada
- função <i>f(.)</i>, ou seja, encontrar (se existir) algum <i>x</i> para o qual <i>f(x)=0</i>.
- Nesse caso, se o interesse estiver em pontos dentro do intervalo <i>[0, 1]</i>, seria inútil se o ponto devolvido
- tivesse um <i style="color:#aa0000;" title="pois se a resposta foi 1/2, na verdade a raíz poderia ser desde o ponto 0 até o ponto 1!">erro
- maior que <i>0,5</i></i> (i.e., <i>|f(x)| > 0,5</i>).
- <br/>
- Outro exemplo numérico é computar uma
- <a href="https://pt.wikipedia.org/wiki/Fun%C3%A7%C3%A3o_transcendente" target="_blank"
- title="função que não pode ser escrita com operações algébricas - ver na WikiPedia">função transcendente</a> como o <i>cosseno</i> de valor real.
- Uma vez as operações que um computador digita consegue efetivamente realizar são aquelas diretamente derivadas da soma, é preciso encontrar um
- algoritmo que compute uma <i>boa aproximação</i> para o <i>cosseno(x)</i>, qualquer que seja o valor real <i>x</i>.
- Neste texto examinaremos as implementações para as funções <i>seno</i> e <i>cosseno</i> empregando o conceito matemático
- <a href="https://pt.wikipedia.org/wiki/S%C3%A9rie_de_Taylor" target="_blank">
- <b style="color:#00aa00;"
- title="um somatório com infinitos termos que aproxima a função por meio de um
- polinômio cuja propriedade é que suas derivadas, de todas as ordens,
- coincidem com as derivadas da função aproximada no ponto dado">série de Taylor</b></a>. <!-- " -->
- </p>
- <p>
- Quanto à <b style="color:#0000aa;">velocidade</b>, ela diz respeito ao <i>tempo que o algoritmo leva para computar a resposta</i>.
- É preciso que o tempo seja viável para o contexto, quer dizer que ao obter a resposta, essa ainda seja útil.
- Para ilustrar podemos considerar novamente o exemplo <i>numérico</i> da determinação da raíz da função <i>f(.)</i> (se existir, encontrar
- algum <i>x</i> para o qual <i>f(x)=0</i>).
- Digamos que o interessado seja uma industria de produção de tintas e que a raíz representa o melhor momento para interromper a
- produção de cada um de seus 300 pigmentos distintos.
- Nesse caso, se a resposta para cada função/pigmento demorar cerca de 10 minutos, então o método empregado é
- inviável, pois seria <i style="color:#aa0000;" title="pois levaria mais de 2 dias para obter a produção ideal para aquele dia!">necessário
- cerca de 50 horas para obter as 300 raízes</i> (<i>50 x 10 min = 3000 min</i>).
- <br/>
- Um exemplo de outra natureza seria o contexto de planejamentos de pousos e aterrissagens em um grande aeroporto, após a ocorrência
- de algum evento não esperado, deve-se gerar uma nova escala para as próxima
- aterrissagens e decolagens muito rapidamente, antes que acabe o combustível de alguma aeronave em sobrevoo.
-
- </p>
- <a name="precisao">
- <p class="secao">Problema de precisão numérica</p>
- </a>
- <p>
- Como discutido no <a href="#" onclick="trocaPagina('introducao_float.html')" title="examinar o texto sobre ponto flutuante">texto sobre
- <i>ponto flutuante</i></a>, o problema de precisão numérica inicia-se na impossibilidade de representar valores
- <i style="color:#00aa00;" title="Impossível representar todos os dígitos de Pi (pois são infinitos)!">irracionais</i>
- e mesmo
- <i style="color:#00aa00;" title="Impossível representar todos os dígios de 1/3 (pois são infinitos)!">dizimas periódicas</i> que precisam
- ser <b style="color:#0000aa;" title="Por exemplo, representar 1/3 por 0,3333 (truncando na quarta casa decimal)">truncados</b>.
- Mas existe outra questão de eficiência, que é a precisão numérica do algoritmo utilizado para o cômputo.
- Um exemplo ilustrativo é acumular valores que crescem muito rapidamente (como funções exponenciais ou fatoriais) ou
- dividir por valores que aproximam-se de zero, ambos podem produzir resultados "ruins":
- <b style="color:#0000aa;" title="estourar o maior valor que o computador tem capacidade para representar (overflow)">estouro</b>
- (ou <i>transbordamento</i>).
- </p>
- <p>
- Como citado acima, um problema computacional é conseguir implementar funções que não podem ser escritas a partir de operações elementares
- de soma e multiplicação, com é o caso da função <i>cosseno</i> (que por isso é transcendental).
- Hoje, este não parece ser um problema, pois qualquer computador de bolso tem uma calculadora implementada que é capaz de computar boas
- aproximações para a função <i>cosseno</i>.
- Entretanto, isso só é possível por implementarem algum algoritmo eficiente, a partir de somas e multiplicações.
- No caso das <i>funções trigonométicas</i>, isso é feito usando a técnica matemática da
- <i title="Na verdade com um truque para tratar todos os períodos">série de Taylor</i>.
- Isso será ilustrado nas tabelas <a href="#solucaoruim" title="seguir para implementação ruim">1 (implementação ineficiente)</a>
- e <a href="#solucaoboa" title="seguir para implementação boa">2 (implementação eficiente)</a>.
- </p>
- <a name="tempo">
- <p class="secao">Eficiência em relação ao tempo de execução</p>
- </a>
- <p>
- Existem problemas que sabidamente são intratáveis no sentido de sabermos, matematicamente, que é impossível existir
- um algoritmo que consegue resolvê-lo (como o <i>problema da parada</i>: não existe algoritmo que possa decidir se um algoritmo qualquer pára ou
- após um tempo finito de execução).
- </p>
- <!--p class="sabermais" title="Para quem deseja saber um pouco mais 'como funciona'"-->
- <p><sabermais title="Para quem deseja saber um pouco mais 'como funciona'">
- Existem outros tipos de problemas que supomos <b style="color:#aa0000;">não</b> ser possível resolvê-los eficientemente, ou seja,
- todas suas soluções conhecidas são ineficientes
- (tempo proporcional à uma função exponencial, de base maior que a unidade).
- Um exemplo nessa categoria, que denominada
- <a href="https://en.wikipedia.org/wiki/NP-completeness" target="_blank" title="Examinar texto na WikiPedia em Inglês">NP-Completo</a>, é o
- <a href="https://pt.wikipedia.org/wiki/Problema_de_satisfatibilidade_booliana" target="_blank"
- title="examinar a explicação na WikiPedia sobre o problema da satisfatibilidade"><i>problema da satisfatibilidade</i></a>:
- encontrar valores para cada uma de suas variáveis, que são <i>booleanas</i>, de modo a tornar a expressão lógica verdadeira,
- como em <i>f(b<sub>1</sub>, b<sub>2</sub>, b<sub>3</sub>) = b<sub>1</sub> E (b<sub>2</sub> OU não b<sub>3</sub>)</i>.
- Para esse caso, por ser uma instância pequena do problema, é fácil perceber que a resposta é <i>sim</i>, basta tomar
- <i>b<sub>1</sub></i> e <i>b<sub>2</sub></i> como <i>verdadeiro</i> (não importando o valor de <i>b<sub>3</sub></i>
- pode ser <i>verdadeiro</i> ou <i>falso</i>).
- A solução conhecida para a <i>satisfatibilidade</i> é tentar todas as <i>2<sup>n<sup></i> possibilidades para as <i>n</i>
- variáveis! Se o <i>n</i> for grande, é inviável esperar por uma resposta.
- Exemplo para <i>n=3</i>: testar com
- <codigo2> <i>f(verdadeiro,verdadeiro,verdadeiro)</i>, <i>f(verdadeiro,verdadeiro,falso)</i>,
- <i>f(verdadeiro,falso, verdadeiro)</i>, <i>f(verdadeiro,falso, falso)</i>,
- <i>f(falso, verdadeiro,verdadeiro)</i>, <i>f(falso, verdadeiro,falso)</i>,
- <i>f(falso, falso, verdadeiro)</i>, <i>f(falso, falso, falso)</i>.</codigo2>
- </sabermais>
- </p>
- <p>
- Na seção seguinte examinaremos um particular problema, conseguir um valor aproximado para a função trigonométrica <i>cosseno</i>,
- explorando tanto a questão da eficiência numérica (precisão) quanto a de tempo de execução (velocidade).
- </p>
- <a name="cosseno">
- <p class="secao"><i>Série de Taylor</i> e a função <i>cosseno</i></p>
- </a>
- <p>
- A função <i>cosseno</i>, assim como várias outras, não pode ser computada de modo exato em um computador digital,
- em razão de ser uma
- <a href="https://pt.wikipedia.org/wiki/Função_transcendente" title="sobre funcao transcendente">função <i>transcendente</i></a>
- (basicamente, significa que não pode ser computada a partir de somas).
- Então usamos uma
- <a href="https://en.wikipedia.org/wiki/Taylor_series" target="_blank"
- title="examinar a explicação na WikiPedia sobre séries de Taylor">teoria matemática apresentada em 1715</a>,
- aproximá-la por uma função polinomial que tem como propriedade ter todas as derivadas
- coincidindo com as derivadas da função a ser aproximada, considerando determinado ponto fixado.
- Esse polinômio de infinitos termos, recebeu o nome de
- <a href="https://pt.wikipedia.org/wiki/S%C3%A9rie_de_Taylor" title="sobre serie de Taylor na WikiPedia"><b>série de Taylor</b></a>,
- devido seu proponente Brook Taylor.
- </p>
- </p>
- O <i>polinômio de Taylor</i> que aproxima a função cosseno quando aplicada ao ponto <i>x</i> é:
- <center>
- <i>cos(x) = 1 - x<sup>2</sup> /2! + x<sup>4</sup> /4! - x<sup>6</sup> /6! + x<sup>8</sup> /8! + ...</i>
- </center>
- Se estiver interessado entender a Matemática para se construir a demostração desse polinômio, estude o bloco abaixo.
- <p>
- <p>
- <sabermais title="Para quem deseja saber um pouco mais: mas precisa conhecer Cálculo Diferencial">
- Como a função <i>cosseno</i> é periódica, de período <i title="letra grega Pi, representa a irracional 3.1415926535897932384626433...">2*Π</i>
- e além disso os valores em cada quadrante coincidem em módulo, podemos computar apenas para primeiro caso, para <i>x</i> entre
- <i>0</i> e <i>Π/2</i>.
- <img class="imgDir" src="img/img_eficiencia_cos_trig.png" title="Imagem (do iGraf) da função trigonométrica cosseno indicando o período 2*Π">
- Desse modo, podemos usar um caso particular da <b>série de Taylor</b>, a séria aplicada ao ponto <i>x=0</i>, que neste caso é chamada
- de
- <a href="https://en.wikipedia.org/wiki/Colin_Maclaurin"
- title="sobre serie de MacLaurin na WikiPedia"><b style="color:#0000aa">série de MacLaurin</b></a>.
- Fixado um valor real <i>x</i> entre <i>0</i> e <i title="letra grega Pi, representa a irracional 3.1415926535897932384626433...">Π/2</i>,
- a <i>série de MacLaurin</i> que aproxima o cosseno aplicado ao ponto <i>x=0</i>, <i>cos(x)</i>, é o polinômio (de infinitos termos)
-
- <i>p(x)=a<sub>0</sub>+a<sub>1</sub>x<sup>1</sup>+a<sub>2</sub>x<sup>2</sup>+a<sub>3</sub>x<sup>3</sup>+...</i>
- de tal forma que
- a <i>k</i>-ésima derivada de <i>p(0)</i> coincide com a <i>k</i>-ésima derivada de <i>cos(0)</i>, ou seja,
- <i>cos(0)=p(0)</i>, <i>cos'(0)=p'(0)</i>, <i>cos''(0)=p''(0)</i> e assim por diante.
- <br/>
- <br/>
- Assim, no caso da função cosseno, escrevendo o polinômio candidato
- <i>p(x) = a<sub>0</sub> x<sup>0</sup> + a<sub>1</sub> x<sup>1</sup> + a<sub>2</sub> x<sup>2</sup> +...</i>
- e estabelecendo as identidades para cada derivada e usando <i>x=0</i> e sabendo que
- <i>cos(0)=1</i>, <i>sen(0)=0</i>, que
- <i>cos'(x)=-sen(x)</i> e que <i>sen'(x)=cos(x)=</i>, obtém-se
- <br/>
- <i>p(0)=a<sub>0</sub>
- ⇒ a<sub>0</sub> = 1<br/>
- <i>p'(0)=a<sub>1</sub>+2a<sub>2</sub>x<sup>1</sup>+3a<sub>3</sub>x<sup>2</sup>+...</i>|x=0
- ⇒ p'(0)=a<sub>1</sub>
- ⇒ a<sub>1</sub> = p'(0)= -sen(0) = 0
- ⇒ a<sub>1</sub> = 0<br/>
- <i>p''(0)=2a<sub>2</sub>+6a<sub>3</sub>x<sup>1</sup>+12a<sub>4</sub>x<sup>2</sup>+...</i>|x=0
- ⇒ (1/2)p''(0)=a<sub>2</sub>
- ⇒ a<sub>2</sub> = (1/2)(-cos(0)) = -1
- ⇒ a<sub>2</sub> = -1/2<br/>
- <i>p'''(0)=6a<sub>3</sub>+24a<sub>4</sub>x<sup>1</sup>+...</i>|x=0
- ⇒ (1/6)p'''(0)=a<sub>3</sub>
- ⇒ a<sub>3</sub> = (1/6)(cos'''(0)) = (1/3!)(-sen''(0)) = (1/3!)(-cos'(0)) = (1/3!)sen(0) = (1/3!)0
- ⇒ a<sub>3</sub> = 0<br/>
- e assim, por diante, obtendo-se o polinômio aproximador:
- <br/>
-
- <i>cos(x) = 1 - x<sup>2</sup> /2! + x<sup>4</sup> /4! - x<sup>6</sup> /6! + x<sup>8</sup> /8! + ...</i>
- </sabermais>
- </p>
- <!--
- ⇒ = implica
- ≃ aproximado
- ✓
- -->
- <a name="cosseno">
- <p class="secao">Eficiência: examinando a função transcendente <i>cosseno</i></p>
- </a>
- <p>
- A implementação da aproximação da função <i>cosseno</i>, próximo do valor nulo, por <i>série de Taylor</i> produz um bom exemplo
- para ilustrar <i>ineficiência</i> e <i>eficiência</i> de algoritmos, quanto ao tempo de execução e quanto à precisão numérica.
- Uma primeira observação é que é necessário truncar a série por duas razões:
- é impossível computar infinitos termos; além disso a partir de determinado termo, todos os seguintes resultam zero (devido à
- precisão finita de qualquer computador).
- Portanto, devemos <i>truncar</i> a <i>série de Taylor</i>.
- </p>
- <p>
- Analisando o polinômio de Taylor para o <i>cosseno</i>, talvez um aprendiz na arte da programação perceba
- que é possível escrever cada termos do somatório com
- <i>(-1)<sub>k</sub>(1/2k!)x<sup>2k</sup></i>, <i>k=0,1,2,...</i>.
- Ou seja, cada termo tem no numerador um cálculo de exponencial (<i>x<sup>2k</sup></i>) e no denominador um cálculo de fatorial (<i>2k!</i>).
- Daí poderia-se desenhar um laço acumulando cada um desses termos.
- <br/>
- Entretanto, essa solução tem um claro problema (que aparecerá no primeiro teste):
- o computo de exponencial (se maior que um) e de fatorial, ambos crescem muito rápido!
- Isso resultará em valores nada próximos do esperado.
- Isso é ilustrado abaixo.
- </p>
- <a name="taylor">
- <p class="secao">Uma primeira solução nada eficiente para a função <i>cosseno</i></p>
- </a>
- <p>
- A partir da série de Taylor para o cosseno, percebe-se que pode-se elaborar um laço que a cada passo computa um dos termos da série, primeiro
- <i>1=x<sup>0</sup>/0!</i>, depois <i>x<sup>2</sup>/2!</i> e assim por diante, para um passo genérico <i>k</i> seria,
- <i>-1<sup>k</sup>x<sup>2k</sup>/(2k!)</i>.
- </p>
- <p>
- Desse modo uma primeira solução, seria implementar duas funções auxiliares, uma para computar a potência (<i>pot</i>) e uma para o
- fatorial (<i>fat</i>), a cada passo invocando as funções auxiliares.
- Vamos simplificar a condição de parada, fixando a <i>priori</i> o número de termos calculados, digamos <i>NT=10</i>.
- <center>
- <a name="solucaoruim">
- <i>Tab. 1. Uma implementação muito ineficiente para a Série de Taylor que aproxima <i>cosseno</i>.</i>
- </a>
- <br/>
- <table class="tbCodeLinCol">
- <tr class="tbCodeLinColH"><th colspan="2">Algoritmo muito ineficiente para cômputo do cosseno</th></tr>
- <tr><th>C </th> <th>Python</th></tr>
- <tr valign="top"><td><table class="tbCode">
- <tr><td><pre>i = 0;
- while (i<2*NT) { <cyan>// Ineficiente! Nao implemente assim!</cyan>
- soma = soma + pot(-1, 2*i) * pot(x, 2*i) / fat(2*i);
- i += 2;
- }</pre></td></tr>
- </table></td>
- <td><table class="tbCode"><pre>i = 0;
- while (i<2*NT) : <cyan># Ineficiente! Nao implemente assim!</cyan>
- soma = soma + pot(-1, 2*i) * pot(x, 2*i) / fat(2*i);
- i += 2;
-
- return soma;</pre></td></tr>
- </table></td></tr>
- </table></center>
- <p>
- <p>
- Testando-se o algoritmo acima, percebe-se rapidamente que o método não é eficiente. Por exemplo, examinando a tabela 1, na qual listamos
- os resultados para vários valores de <i>x</i> empregando o método acima, um método eficiente (vistao abaixo) e a função <i>cosseno</i>
- da biblioteca da linguagem.
- Nota-se que já na segunda linha, para <i>x=0.05</i>, o método ineficiente gera o valor <i>1.0012</i> enquanto o eficiente e a biblioteca
- <i>0.9988</i>, uma diferença nada desprezível.
- Isso vai piorando com o aumento de <i>x</i>, para o último tabelado, <i>x=0.45</i>, a diferença já está em quase <i>0.2</i>!
- </p>
- <p>
- <b>Ineficiência 1</b>.
- Fazendo um rápido exame da solução notamos um primeiro item de ineficiência:
- computar <i>pot(-1, 2*i)</i> a cada passo.
- Isso é completamente desnecessário, pois de um passo ao outro deve-se apenas inverter o sinal, então bastaria usar uma variável
- <tt>sinal</tt>,
- que a cada passo fizesse <tt>sinal = -sinal</tt>.
- </p>
- <p>
- <b>Ineficiência 2</b>.
- Note que a mesma ineficiência do sinal ocorre com <tt>pot</tt> e <tt>fat</tt>, pois a cada passo ignora-se que uma potência e um fatorial
- parcial já foi computado, calculando tudo desde o primeiro valor.
- Se ainda não está claro que pode ser melhorado, vamos examinar os passos <tt>k=2</tt> e <tt>k=3</tt>:
- <br/>
- - <tt>k=2</tt>: computa-se <tt>pot(x, 2*2)=pot(x, 4)</tt> e <tt>fat(2*2)=fat(4)</tt>; e<br/>
- - <tt>k=3</tt>: computa-se <tt>pot(x, 2*3)=pot(x, 6)</tt> e <tt>fat(2*3)=fat(6)</tt>.<br/>
- Mas, se usarmos uma variável <tt>pot1</tt> para manter a potência e <tt>fat1</tt> para manter o fatorial,
- <!--
- <i>x<sup>2k</sup></i> (<tt>pot(x, 2*k)</tt>) e
- <tt>fat1</tt> para manter <i>2k!</i> (<tt>fat(2*k)</tt>),
- -->
- então, ao final do passo <tt>k=2</tt>,
- <tt>pot1</tt> estaria com o valor <i>x<sup>2k</sup>=x<sup>4</sup></i> e <tt>fat1</tt> com o valor <i>2k!=4!=24</i>.
- <br/>
- Portanto, no passo <tt>k=3</tt>, bastaria fazer:
- <center>
- <tt>pot1 = pot1 * x * x</tt> e <tt>fat1 = fat1 * 5 * 6</tt>,
- </center>
- pois dessa forma, conseguiríamos
- <center><!-- ⇒ = implica -->
- <tt>pot1 ← pot1 * x * x ⇒ pot1 ← x<sup>4</sup> x<sup>2</sup> = x<sup>6</sup></tt> e
- <br/>
- <tt>fat1 ← fat1 * 5 * 6 ⇒ fat1 ← 4! * 5 * 6 = 6!</tt>.
- </center>
- </p>
- <p>
- Outro problema da solução da tabela 1 é que potência e fatorial crescem muito rápido
- (se <i>x > 1</i>, mas se <i>x < 1</i>, então decresce muito rápido).
- Isso também pode resultar ineficiência numérica.
- </p>
- <p>
- Portanto, esta solução 1 é muito ineficaz, tanto em termos de velocidade quanto de precisão.
- A discussão da razão de sua ineficácia já indica um caminho de melhoria, examinado a seguir.
- </p>
- <a name="ingenuo">
- <p class="secao">Uma segunda solução para a função <i>cosseno</i>: ainda ineficaz numericamente</p>
- </a>
- <p>
- Uma solução um pouco melhor, mas numericamente ainda ingênua, seria eliminar a grande ineficiência citada acima, a cada passo
- aproveitando a potência e o fatorial anterior.
- Usando como critério de parada que as diferenças entre o cômputo do termo da série seja menor que um dado limiar <i>Lim</i>,
- o código pode ficar como o mostrado abaixo. Suporemos a existëncia de uma função que devolve o módulo de um valor "flutuante", de nome <i>valor_absoluto</i>:
- <center>
- <a name="solucaomedia">
- <i>Tab. 2. Uma implementação não tão ineficiente para a Série de Taylor que aproxima <i>cosseno</i>.</i>
- </a>
- <br/>
- <table class="tbCodeLinCol">
- <tr class="tbCodeLinColH"><th colspan="2">Algoritmo ineficiente para cômputo do cosseno</th></tr>
- <tr><th>C </th> <th>Python</th></tr>
- <tr valign="top"><td><table class="tbCode">
- <tr><td><pre><verm>float</verm> cossInef (<verm>float</verm> x) {
- <verm>float</verm> pot = 1, soma = 0;
- <verm>float</verm> termo0 = 2, termo = 1;
- <verm>int</verm> fat = 1, i = 1, sinal = 1;
- while (valor_absoluto(termo0-termo)>Lim) {
- soma = soma + termo;
- pot *= x * x; <cyan>// compute potencia 2i de x</cyan>
- fat *= (i+1) * (i+2); <cyan>// compute fatorial de 2i</cyan>
- termo0 = termo;
- termo = sinal * pot / fat;
- i += 2;
- sinal = -sinal;
- }
- return soma;
- }</pre></td></tr>
- </table></td>
- <td><table class="tbCode"><pre><verm>def</verm> cossInef (x) :
- pot = 1; soma = 0;
- termo0 = 2; termo = 1;
- fat = 1; i = 0; sinal = 1;
- while (valor_absoluto(termo0-termo)>Lim) :
- soma = soma + termo;
- pot *= x * x; <cyan># compute potencia 2i de x</cyan>
- fat *= (i+1) * (i+2); <cyan># compute fatorial de 2i</cyan>
- termo0 = termo;
- termo = sinal * pot / fat;
- i += 2;
- sinal = -sinal;
- return soma;</pre></td></tr>
- </table></td></tr>
- </table></center>
- </p>
- <p>
- Rodando este algoritmo e comparando-o com a implementação interna das linguagens <b>C</b> e <b>Python</b>, mesmo para valores
- próximos de zero (como <i>0.1</i>) nota-se uma diferença significativa de <i>0.01</i> (veja a tabela abaixo onde comparamos o algoritmo
- acima, sua versão mais eficiente e a implementação interna da linguagem).
- Isso indica que o método acima <b>não</b> é eficiente.
- </p>
- <p>
- Analisando-se o algoritmo, percebe-se que o potencial problema é que a variável <i>termo</i> é resultado da divisão de 2 valores que tem
- tem potencial de crescimento enorme (as variáveis <i>pot</i> e <i>fat</i> - com valor <i>x</i> próximo de zero, <i>pot</i> na verdade
- aproxima-se muito rápido é do zero).
- <p>
- </p>
- Outra observação importante: a diferença entre 2 passos no laço para a variável <i>termo</i> é multiplicar por <i>x * x / (i+1)*(i+2)</i>.
- Assim, poderia-se tentar a cada passo multiplicar diretamente por esta "diferença" e não acumular a potência e o fatorial!
- </p>
- <a name="eficiente">
- <p class="secao">Uma solução eficiente para a função <i>cosseno</i></p>
- </a>
- <p>
- Usando esta ideia produz-se o algoritmo seguinte que é bem mais eficiente. Nele aproveitamos para ilustrar o uso
- de medição de tempo de execução tanto em <b>C</b> quanto em <b>Python</b>,
- <center>
- <a name="solucaoboa">
- <i>Tab. 3. Uma implementação bem eficiente para a Série de Taylor que aproxima <i>cosseno</i>.</i>
- </a>
- <br/>
- <table class="tbCodeLinCol">
- <tr class="tbCodeLinColH"><th colspan="2">Algoritmo eficiente para cômputo do cosseno - comparação com o ineficiente e o interno</th></tr>
- <tr><th>C </th> <th>Python</th></tr>
- <tr valign="top"><td><table class="tbCode">
- <tr><td><pre><cyan>// Leo^nidas O. Brandao - 2017/06/19</cyan>
- <cyan>// cos(x) = 0! x^0 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
- <cyan>// = 1 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
- <cyan>// gcc -lm -o introducao_eficiencia_algoritmos_cosseno introducao_eficiencia_algoritmos_cosseno.c</cyan>
- <incl1>#include</incl1> <stdio.h>
- <incl1>#include</incl1> <math.h> <cyan>// cos(x)</cyan>
- <incl1>#include</incl1> <time.h> <cyan>// clock_t, clock()</cyan>
- <def>#define</def> Lim 0.001
- <verm>float</verm> valor_absoluto (<verm>float</verm> x) {
- if (x>=0) return x;
- return -x;
- }
- <cyan>// Cosseno implementado de modo ineficiente</cyan>
- <verm>float</verm> cossInef (<verm>float</verm> x) { ... } <cyan>// copiar aqui o codigo C do ineficiente acima</cyan>
- <cyan>// Cosseno implementado de modo mais eficiente</cyan>
- <verm>float</verm> cossEfic (<verm>float</verm> x) {
- <verm>float</verm> pot = 1, soma = 0;
- <verm>float</verm> termo0 = 2, termo = 1;
- <verm>int</verm> i = 0, sinal = 1;
- while (valor_absoluto(termo0-termo)>Lim) {
- soma = soma + termo;
- termo0 = termo;
- termo *= -1 * x * x / ((i+1) * (i+2)); <cyan>// compute potencia 2i de x e fatorial de 2i</cyan>
- i += 2;
- }
- return soma;
- }
- <verm>int</verm> main (void) {
- <verm>float</verm> x, cosx, auxcI, auxcE;
- <verm>int</verm> i;
- clock_t tempo_inicial = clock(); <cyan>// "dispara o cronometro"...</cyan>
- <verd>printf</verd>(" x | ineficiente | eficiente | math cos(x) | dist inef. | dist efic.\n");
- x = 0.0;
- for (i=0; i<10; i++) {
- cosx = cos(x);
- auxcI = cossInef(x);
- auxcE = cossEfic(x);
- <verd>printf</verd>(" %3.2f | %11.4f | %9.4f | %11.4f | %10.4f | %10.4f\n",
- x, auxcI, auxcE, cosx, valor_absoluto(auxcI-cosx), valor_absoluto(auxcE-cosx));
- x += 0.05;
- }
- clock_t tempo_final = clock();
- <verd>printf</verd>("Tempo em segundos: %f\n", (double)(tempo_final - tempo_inicial) / CLOCKS_PER_SEC);
- return 1;
- }</pre></td></tr>
- </table></td>
- <td><table class="tbCode"><pre><cyan># Leo^nidas O. Brandao - 2017/06/19</cyan>
- <cyan># cos(x) = 0! x^0 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
- <cyan># = 1 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
- <cyan># Invocar execucao com linha de comando: python introducao_eficiencia_algoritmos_cosseno.py</cyan>
- <incl1>import</incl1> math; <cyan># para cos(x)</cyan>
- <incl1>import</incl1> time; <cyan># para tempo 'time.time()'</cyan>
- Lim = 0.001
- <verm>def</verm> valor_absoluto (x) :
- if (x>=0) : return x;
- return -x;
- <cyan># Cosseno implementado de modo mais eficiente</cyan>
- <verm>def</verm> cossInef (x) : <cyan># Copiar aqui a implementacao de cosseno ineficiente</cyan>
- ...
-
- <cyan># Cosseno implementado de modo mais eficiente</cyan>
- <verm>def</verm> cossEfic (x) :
- pot = 1; soma = 0;
- termo0 = 2; termo = 1;
- i = 0; sinal = 1;
- while (valor_absoluto(termo0-termo)>Lim) :
- soma = soma + termo;
- termo0 = termo;
- termo *= -1 * x * x / ((i+1) * (i+2)); <cyan># compute potencia 2i de x e fatorial de 2i</cyan>
- i += 2;
- return soma;
- <verm>def</verm> main () :
- tempo_inicial = time.time(); <cyan># "dispara o cronometro"...</cyan>
- <verd>print</verd>(" x | ineficiente | eficiente | math cos(x) | dist inef. | dist efic.");
- x = 0.0;
- for i in range(0, 10) :
- cosx = math.cos(x);
- auxcI = cossInef(x);
- auxcE = cossEfic(x);
- <verd>print</verd>(" %3.2f | %11.4f | %9.4f | %11.4f | %10.4f | %10.4f" %
- ( x, auxcI, auxcE, cosx, valor_absoluto(auxcI-cosx), valor_absoluto(auxcE-cosx) ));
- x += 0.05;
- tempo_final = time.time();
- <verd>print</verd>("Tempo em segundos: %f\n" % (tempo_final - tempo_inicial));
- main();</pre></td></tr>
- </table></td></tr>
- </table></center>
- </p>
- <p>
- Experimente rodar em sua máquina o código acima (completanto com o código da função <i>cossInef</i>, implementada na tab. 1).
- Na máquina usada em 2017 para o primeiro teste desse código, tanto rodando <i>C</i> quanto <i>Python</i>, produzem como resultado a tabela abaixo.
- Note que a medida
- que o <i>x</i> se afasta da origem, aparece mais erro, entretanto os erros são consistentemente maiores ao usar
- a implementação ingênua.
- <center>
- <i>Tab. 4. As saídas geradas pelos códigos ineficientes e eficientes (da tabela 3) para aproximar <i>cosseno</i>.</i>
- <pre> x | ineficiente | eficiente | math cos(x) | dist inef. | dist efic.
- 0.00 | 1.0000 | 1.0000 | 1.0000 | 0.0000 | 0.0000
- 0.05 | 1.0012 | 0.9988 | 0.9988 | 0.0025 | 0.0000
- 0.10 | 1.0050 | 0.9950 | 0.9950 | 0.0100 | 0.0000
- 0.15 | 1.0112 | 0.9888 | 0.9888 | 0.0225 | 0.0000
- 0.20 | 1.0199 | 0.9801 | 0.9801 | 0.0399 | 0.0000
- 0.25 | 1.0311 | 0.9689 | 0.9689 | 0.0622 | 0.0000
- 0.30 | 1.0447 | 0.9553 | 0.9553 | 0.0893 | 0.0000
- 0.35 | 1.0606 | 0.9394 | 0.9394 | 0.1213 | 0.0000
- 0.40 | 1.0789 | 0.9211 | 0.9211 | 0.1579 | 0.0000
- 0.45 | 1.0996 | 0.9004 | 0.9004 | 0.1991 | 0.0000</pre></center>
- </p>
- <p>
- Os resultados numéricos são os mesmos comparando o executável a partir do código <i>C</i> ou a interpretação do <i>Python</i>,
- Entretanto, os tempos de execução diferem,
- sendo significativamente menores utilizando a versão compilada (compilador <b>Gnu GCC</b>).
- Usando a mesma máquina (um PC com 2 processadores AMD Phenom II rodando Linux/Debian 8.8),
- os menores resultados de tempo de execução (dentre 100 testes) para o executável via <i>C</i> e o interpretado
- <i>Python</i>: <center><pre>
- C Python
- Tempo em segundos: 0.000079 0.000220</pre></center>
- </p>
- <p>
- Em novos testes, desta vez usando um computador de dois núcleos com processador <i>Intel Core i5-6400 CPU @ 2.70GHz</i>,
- com sistema <i>Debian GNU/Linux 9.12</i>, rodando 400 vezes, eliminando-se as linhas com comando de impressão
- (pois as saídas poderiam uniformizar os resultados por serem significativamente mais lentas),
- os resultados numéricos do executável (compilação <i>Gnu GCCC</i>) mostra um bastante superior ao interpretador <i>Python</i>.
- Os tempos médios das 400 rodadas, do menor tempo de execução e do maior tempo, para a versão compilada e interpretada foram
- (em segundos):
- <center><pre>
- C Python
- Tempo medio: 0.000520 0.008276
- Tempo minimo: 0.000016 0.000076
- Tempo maximo: 0.000945 0.016031</pre></center>
- </p>
- <!--
- 2020/08/13 - /proc/cpuinfo = 2 x Intel(R) Core(TM) i5-6400 CPU @ 2.70GHz
- C
- -->
- <p>
- Este é apenas um resumo para apresentar o assunto de eficiência de algoritmos, seu aprofundamente demanda muito mais tempo.
- Por exemplo, no IME-USP existe um curso (usualmente de segundo ano) cujo objeto de estudo é precisamente
- discutir efeciência de algoritmos, o curso
- <a href="http://uspdigital.usp.br/jupiterweb/obterDisciplina?sgldis=MAC0122&verdis=2"
- title="ver ementa">MAC0122 - Princípios de Desenvolvimento de Algoritmos</a>.
- No programa de Verão do IME-USP temos o curso
- <a href="http://www.ime.usp.br/verao/index.php/turmas/apresentacao.php"
- title="ver lista de cursos do ultimo Verao">Tópicos de Programação</a>.
- </p>
- <p class="autoria">
- <a href="https://www.ime.usp.br/~leo" target="_blank" title="seguir para a pagina do prof. Leônidas">Leônidas de Oliveira Brandão</a><br/>
- <a href="http://www.ime.usp.br/~leo" target="_blank" title="seguir para a página do LInE">http://line.ime.usp.br</a>
- </p>
- <p class="rodape">
- <b>Alterações</b>:<br/>
- 2020/08/15: novo formato, pequenas revisões<br/>
- 2020/08/13: ampla revisão (introdução nova, com imagem), novo formato;<br/>
- 2019/04/24: primeira versão
- </p>
- </div>
|