introducao_eficiencia_algoritmos.html 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. <!--
  2. Introdução à Programação - 2017 - Prof. Leoônidas de Oliveira Brandão
  3. Introdução à eficiência de algoritmos
  4. LInE (Laboratory of Informatics in Education) - http://www.usp.br/line
  5. IME - USP
  6. Material didático
  7. Pode usar livrevemente este material para fins não comerciais, devendo sempre fazer referência à autoria.
  8. Sugestões/apontamento são bem vindos: leo@ime.usp.br (favor indicar no assunto "material de introducao 'a programacao")
  9. Prof. Leônidas de Oliveira Brandão
  10. http://www.ime.usp.br/~leo
  11. http://line.ime.usp.br
  12. http://www.matemtica.br
  13. -->
  14. <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
  15. <meta name='keywords' content='mac0122, material, professores, leonidas de oliveira brandao'>
  16. <link rel='stylesheet' type='text/css' href='css_img_js_conf/all.css'>
  17. <link rel='stylesheet' type='text/css' href='css_img_js_conf/line_introducao_programacao.css'>
  18. <script src="css_img_js_conf/defineLInE.js"></script> <!-- para referencias 'a documentos internos -->
  19. <div class="pagina">
  20. <p class="secao">Introdução à eficiência de algoritmos</p>
  21. <center><p>[
  22. <a href="#precisao" title="Problema de precisao numerica">Precisão</a> &nbsp; | &nbsp;
  23. <a href="#tempo" title="Eficiencia em relacao ao tempo de execucao">Velocidade</a> &nbsp; | &nbsp;
  24. <a href="#cosseno" title="Eficiencia: examinando a funcao transcendente <i>cosseno</i>">Cosseno</a> &nbsp; | &nbsp;
  25. <a href="#muitoingenuo" title="Uma primeira solucao nada eficiente para a funcao <i>cosseno</i>">Solução ineficiente</a> &nbsp; | &nbsp;
  26. <a href="#ingenuo" title="Uma segunda solucao para a funcao <i>cosseno</i>: ainda ineficaz">Solução</a> &nbsp; | &nbsp;
  27. <a href="#eficiente" title="Uma solucao eficiente para a funcao <i>cosseno</i>">Solução eficiente</a>
  28. ]</p>
  29. </center>
  30. <!--
  31. <p>
  32. <b>Alterações</b>: //2017 <i>C</i> e <i>Python</i>)
  33. </p>
  34. -->
  35. <p>
  36. Nesta seção examinaremos uma questão essencial à computação, se o algoritmo é ou não eficiente para resolver o problema de interesse.
  37. Sobre isso existem vários aspectos que podem ser considerados, aqui examinaremos dois: a <b>precisão</b> e a <b>velocidade</b>.
  38. <center>
  39. <img src="img/img_precisao_x_velocidade.png" title="Gráfico ilustrando que geralmente precisão varia inversamente com a velocidade"/>
  40. <br/>
  41. <i>Fig. 1. Representação de que maior velocidade de execução geralmente implica em menor precisão numérica.</i>
  42. </center>
  43. </p>
  44. <p>
  45. A <b style="color:#0000aa;">precisão</b> diz respeito à "qualidade" da resposta, é necessário que ela seja
  46. <b style="color:#00aa00;">suficientemente próxima da real solução</b> para o problema considerado.
  47. Para ilustrar podemos considerar um exemplo <i>numérico</i>, por exemplo, tentar encontrar a raíz para determinada
  48. função <i>f(.)</i>, ou seja, encontrar (se existir) algum <i>x</i> para o qual <i>f(x)=0</i>.
  49. Nesse caso, se o interesse estiver em pontos dentro do intervalo <i>[0, 1]</i>, seria inútil se o ponto devolvido
  50. 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
  51. maior que <i>0,5</i></i> (i.e., <i>|f(x)| > 0,5</i>).
  52. <br/>
  53. Outro exemplo numérico é computar uma
  54. <a href="https://pt.wikipedia.org/wiki/Fun%C3%A7%C3%A3o_transcendente" target="_blank"
  55. 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.
  56. Uma vez as operações que um computador digita consegue efetivamente realizar são aquelas diretamente derivadas da soma, é preciso encontrar um
  57. algoritmo que compute uma <i>boa aproximação</i> para o <i>cosseno(x)</i>, qualquer que seja o valor real <i>x</i>.
  58. Neste texto examinaremos as implementações para as funções <i>seno</i> e <i>cosseno</i> empregando o conceito matemático
  59. <a href="https://pt.wikipedia.org/wiki/S%C3%A9rie_de_Taylor" target="_blank">
  60. <b style="color:#00aa00;"
  61. title="um somatório com infinitos termos que aproxima a função por meio de um
  62. polinômio cuja propriedade é que suas derivadas, de todas as ordens,
  63. coincidem com as derivadas da função aproximada no ponto dado">série de Taylor</b></a>. <!-- " -->
  64. </p>
  65. <p>
  66. Quanto à <b style="color:#0000aa;">velocidade</b>, ela diz respeito ao <i>tempo que o algoritmo leva para computar a resposta</i>.
  67. É preciso que o tempo seja viável para o contexto, quer dizer que ao obter a resposta, essa ainda seja útil.
  68. 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
  69. algum <i>x</i> para o qual <i>f(x)=0</i>).
  70. Digamos que o interessado seja uma industria de produção de tintas e que a raíz representa o melhor momento para interromper a
  71. produção de cada um de seus 300 pigmentos distintos.
  72. Nesse caso, se a resposta para cada função/pigmento demorar cerca de 10 minutos, então o método empregado é
  73. 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
  74. cerca de 50 horas para obter as 300 raízes</i> (<i>50 x 10 min = 3000 min</i>).
  75. <br/>
  76. Um exemplo de outra natureza seria o contexto de planejamentos de pousos e aterrissagens em um grande aeroporto, após a ocorrência
  77. de algum evento não esperado, deve-se gerar uma nova escala para as próxima
  78. aterrissagens e decolagens muito rapidamente, antes que acabe o combustível de alguma aeronave em sobrevoo.
  79. </p>
  80. <a name="precisao">
  81. <p class="secao">Problema de precisão numérica</p>
  82. </a>
  83. <p>
  84. Como discutido no <a href="#" onclick="trocaPagina('introducao_float.html')" title="examinar o texto sobre ponto flutuante">texto sobre
  85. <i>ponto flutuante</i></a>, o problema de precisão numérica inicia-se na impossibilidade de representar valores
  86. <i style="color:#00aa00;" title="Impossível representar todos os dígitos de Pi (pois são infinitos)!">irracionais</i>
  87. e mesmo
  88. <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
  89. ser <b style="color:#0000aa;" title="Por exemplo, representar 1/3 por 0,3333 (truncando na quarta casa decimal)">truncados</b>.
  90. Mas existe outra questão de eficiência, que é a precisão numérica do algoritmo utilizado para o cômputo.
  91. Um exemplo ilustrativo é acumular valores que crescem muito rapidamente (como funções exponenciais ou fatoriais) ou
  92. dividir por valores que aproximam-se de zero, ambos podem produzir resultados "ruins":
  93. <b style="color:#0000aa;" title="estourar o maior valor que o computador tem capacidade para representar (overflow)">estouro</b>
  94. (ou <i>transbordamento</i>).
  95. </p>
  96. <p>
  97. Como citado acima, um problema computacional é conseguir implementar funções que não podem ser escritas a partir de operações elementares
  98. de soma e multiplicação, com é o caso da função <i>cosseno</i> (que por isso é transcendental).
  99. Hoje, este não parece ser um problema, pois qualquer computador de bolso tem uma calculadora implementada que é capaz de computar boas
  100. aproximações para a função <i>cosseno</i>.
  101. Entretanto, isso só é possível por implementarem algum algoritmo eficiente, a partir de somas e multiplicações.
  102. No caso das <i>funções trigonométicas</i>, isso é feito usando a técnica matemática da
  103. <i title="Na verdade com um truque para tratar todos os períodos">série de Taylor</i>.
  104. Isso será ilustrado nas tabelas <a href="#solucaoruim" title="seguir para implementação ruim">1 (implementação ineficiente)</a>
  105. e <a href="#solucaoboa" title="seguir para implementação boa">2 (implementação eficiente)</a>.
  106. </p>
  107. <a name="tempo">
  108. <p class="secao">Eficiência em relação ao tempo de execução</p>
  109. </a>
  110. <p>
  111. Existem problemas que sabidamente são intratáveis no sentido de sabermos, matematicamente, que é impossível existir
  112. 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
  113. após um tempo finito de execução).
  114. </p>
  115. <!--p class="sabermais" title="Para quem deseja saber um pouco mais 'como funciona'"-->
  116. <p><sabermais title="Para quem deseja saber um pouco mais 'como funciona'">
  117. Existem outros tipos de problemas que supomos <b style="color:#aa0000;">não</b> ser possível resolvê-los eficientemente, ou seja,
  118. todas suas soluções conhecidas são ineficientes
  119. (tempo proporcional à uma função exponencial, de base maior que a unidade).
  120. Um exemplo nessa categoria, que denominada
  121. <a href="https://en.wikipedia.org/wiki/NP-completeness" target="_blank" title="Examinar texto na WikiPedia em Inglês">NP-Completo</a>, é o
  122. <a href="https://pt.wikipedia.org/wiki/Problema_de_satisfatibilidade_booliana" target="_blank"
  123. title="examinar a explicação na WikiPedia sobre o problema da satisfatibilidade"><i>problema da satisfatibilidade</i></a>:
  124. 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,
  125. 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>.
  126. Para esse caso, por ser uma instância pequena do problema, é fácil perceber que a resposta é <i>sim</i>, basta tomar
  127. <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>
  128. pode ser <i>verdadeiro</i> ou <i>falso</i>).
  129. A solução conhecida para a <i>satisfatibilidade</i> é tentar todas as <i>2<sup>n<sup></i> possibilidades para as <i>n</i>
  130. variáveis! Se o <i>n</i> for grande, é inviável esperar por uma resposta.
  131. Exemplo para <i>n=3</i>: testar com
  132. <codigo2> &nbsp; &nbsp; <i>f(verdadeiro,verdadeiro,verdadeiro)</i>, <i>f(verdadeiro,verdadeiro,falso)</i>,
  133. &nbsp; &nbsp; <i>f(verdadeiro,falso, verdadeiro)</i>, <i>f(verdadeiro,falso, falso)</i>,
  134. &nbsp; &nbsp; <i>f(falso, verdadeiro,verdadeiro)</i>, <i>f(falso, verdadeiro,falso)</i>,
  135. &nbsp; &nbsp; <i>f(falso, falso, verdadeiro)</i>, <i>f(falso, falso, falso)</i>.</codigo2>
  136. </sabermais>
  137. </p>
  138. <p>
  139. Na seção seguinte examinaremos um particular problema, conseguir um valor aproximado para a função trigonométrica <i>cosseno</i>,
  140. explorando tanto a questão da eficiência numérica (precisão) quanto a de tempo de execução (velocidade).
  141. </p>
  142. <a name="cosseno">
  143. <p class="secao"><i>Série de Taylor</i> e a função <i>cosseno</i></p>
  144. </a>
  145. <p>
  146. A função <i>cosseno</i>, assim como várias outras, não pode ser computada de modo exato em um computador digital,
  147. em razão de ser uma
  148. <a href="https://pt.wikipedia.org/wiki/Função_transcendente" title="sobre funcao transcendente">função <i>transcendente</i></a>
  149. (basicamente, significa que não pode ser computada a partir de somas).
  150. Então usamos uma
  151. <a href="https://en.wikipedia.org/wiki/Taylor_series" target="_blank"
  152. title="examinar a explicação na WikiPedia sobre séries de Taylor">teoria matemática apresentada em 1715</a>,
  153. aproximá-la por uma função polinomial que tem como propriedade ter todas as derivadas
  154. coincidindo com as derivadas da função a ser aproximada, considerando determinado ponto fixado.
  155. Esse polinômio de infinitos termos, recebeu o nome de
  156. <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>,
  157. devido seu proponente Brook Taylor.
  158. </p>
  159. </p>
  160. O <i>polinômio de Taylor</i> que aproxima a função cosseno quando aplicada ao ponto <i>x</i> é:
  161. <center>
  162. <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>
  163. </center>
  164. Se estiver interessado entender a Matemática para se construir a demostração desse polinômio, estude o bloco abaixo.
  165. <p>
  166. <p>
  167. <sabermais title="Para quem deseja saber um pouco mais: mas precisa conhecer Cálculo Diferencial">
  168. Como a função <i>cosseno</i> é periódica, de período <i title="letra grega Pi, representa a irracional 3.1415926535897932384626433...">2*&Pi;</i>
  169. e além disso os valores em cada quadrante coincidem em módulo, podemos computar apenas para primeiro caso, para <i>x</i> entre
  170. <i>0</i> e <i>&Pi;/2</i>.
  171. <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*&Pi;">
  172. 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
  173. de
  174. <a href="https://en.wikipedia.org/wiki/Colin_Maclaurin"
  175. title="sobre serie de MacLaurin na WikiPedia"><b style="color:#0000aa">série de MacLaurin</b></a>.
  176. Fixado um valor real <i>x</i> entre <i>0</i> e <i title="letra grega Pi, representa a irracional 3.1415926535897932384626433...">&Pi;/2</i>,
  177. 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)
  178. <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>
  179. de tal forma que
  180. 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,
  181. <i>cos(0)=p(0)</i>, <i>cos'(0)=p'(0)</i>, <i>cos''(0)=p''(0)</i> e assim por diante.
  182. <br/>
  183. <br/>
  184. Assim, no caso da função cosseno, escrevendo o polinômio candidato
  185. <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>
  186. e estabelecendo as identidades para cada derivada e usando <i>x=0</i> e sabendo que
  187. <i>cos(0)=1</i>, <i>sen(0)=0</i>, que
  188. <i>cos'(x)=-sen(x)</i> e que <i>sen'(x)=cos(x)=</i>, obtém-se
  189. <br/>
  190. &nbsp; &nbsp; <i>p(0)=a<sub>0</sub>
  191. &rArr; a<sub>0</sub> = 1<br/>
  192. &nbsp; &nbsp; <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
  193. &rArr; p'(0)=a<sub>1</sub>
  194. &rArr; a<sub>1</sub> = p'(0)= -sen(0) = 0
  195. &rArr; a<sub>1</sub> = 0<br/>
  196. &nbsp; &nbsp; <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
  197. &rArr; (1/2)p''(0)=a<sub>2</sub>
  198. &rArr; a<sub>2</sub> = (1/2)(-cos(0)) = -1
  199. &rArr; a<sub>2</sub> = -1/2<br/>
  200. &nbsp; &nbsp; <i>p'''(0)=6a<sub>3</sub>+24a<sub>4</sub>x<sup>1</sup>+...</i>|x=0
  201. &rArr; (1/6)p'''(0)=a<sub>3</sub>
  202. &rArr; a<sub>3</sub> = (1/6)(cos'''(0)) = (1/3!)(-sen''(0)) = (1/3!)(-cos'(0)) = (1/3!)sen(0) = (1/3!)0
  203. &rArr; a<sub>3</sub> = 0<br/>
  204. e assim, por diante, obtendo-se o polinômio aproximador:
  205. <br/>
  206. &nbsp; &nbsp; &nbsp; &nbsp;
  207. <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>
  208. </sabermais>
  209. </p>
  210. <!--
  211. &rArr; = implica
  212. &sime; aproximado
  213. &check;
  214. -->
  215. <a name="cosseno">
  216. <p class="secao">Eficiência: examinando a função transcendente <i>cosseno</i></p>
  217. </a>
  218. <p>
  219. 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
  220. 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.
  221. Uma primeira observação é que é necessário truncar a série por duas razões:
  222. é impossível computar infinitos termos; além disso a partir de determinado termo, todos os seguintes resultam zero (devido à
  223. precisão finita de qualquer computador).
  224. Portanto, devemos <i>truncar</i> a <i>série de Taylor</i>.
  225. </p>
  226. <p>
  227. Analisando o polinômio de Taylor para o <i>cosseno</i>, talvez um aprendiz na arte da programação perceba
  228. que é possível escrever cada termos do somatório com
  229. <i>(-1)<sub>k</sub>(1/2k!)x<sup>2k</sup></i>, <i>k=0,1,2,...</i>.
  230. 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>).
  231. Daí poderia-se desenhar um laço acumulando cada um desses termos.
  232. <br/>
  233. Entretanto, essa solução tem um claro problema (que aparecerá no primeiro teste):
  234. o computo de exponencial (se maior que um) e de fatorial, ambos crescem muito rápido!
  235. Isso resultará em valores nada próximos do esperado.
  236. Isso é ilustrado abaixo.
  237. </p>
  238. <a name="taylor">
  239. <p class="secao">Uma primeira solução nada eficiente para a função <i>cosseno</i></p>
  240. </a>
  241. <p>
  242. 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
  243. <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,
  244. <i>-1<sup>k</sup>x<sup>2k</sup>/(2k!)</i>.
  245. </p>
  246. <p>
  247. 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
  248. fatorial (<i>fat</i>), a cada passo invocando as funções auxiliares.
  249. Vamos simplificar a condição de parada, fixando a <i>priori</i> o número de termos calculados, digamos <i>NT=10</i>.
  250. <center>
  251. <a name="solucaoruim">
  252. <i>Tab. 1. Uma implementação muito ineficiente para a Série de Taylor que aproxima <i>cosseno</i>.</i>
  253. </a>
  254. <br/>
  255. <table class="tbCodeLinCol">
  256. <tr class="tbCodeLinColH"><th colspan="2">Algoritmo muito ineficiente para cômputo do cosseno</th></tr>
  257. <tr><th>C </th> <th>Python</th></tr>
  258. <tr valign="top"><td><table class="tbCode">
  259. <tr><td><pre>i = 0;
  260. while (i&lt;2*NT) { <cyan>// Ineficiente! Nao implemente assim!</cyan>
  261. soma = soma + pot(-1, 2*i) * pot(x, 2*i) / fat(2*i);
  262. i += 2;
  263. }</pre></td></tr>
  264. </table></td>
  265. <td><table class="tbCode"><pre>i = 0;
  266. while (i&lt;2*NT) : <cyan># Ineficiente! Nao implemente assim!</cyan>
  267. soma = soma + pot(-1, 2*i) * pot(x, 2*i) / fat(2*i);
  268. i += 2;
  269. return soma;</pre></td></tr>
  270. </table></td></tr>
  271. </table></center>
  272. <p>
  273. <p>
  274. Testando-se o algoritmo acima, percebe-se rapidamente que o método não é eficiente. Por exemplo, examinando a tabela 1, na qual listamos
  275. 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>
  276. da biblioteca da linguagem.
  277. 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
  278. <i>0.9988</i>, uma diferença nada desprezível.
  279. 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>!
  280. </p>
  281. <p>
  282. <b>Ineficiência 1</b>.
  283. Fazendo um rápido exame da solução notamos um primeiro item de ineficiência:
  284. computar <i>pot(-1, 2*i)</i> a cada passo.
  285. Isso é completamente desnecessário, pois de um passo ao outro deve-se apenas inverter o sinal, então bastaria usar uma variável
  286. <tt>sinal</tt>,
  287. que a cada passo fizesse <tt>sinal = -sinal</tt>.
  288. </p>
  289. <p>
  290. <b>Ineficiência 2</b>.
  291. 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
  292. parcial já foi computado, calculando tudo desde o primeiro valor.
  293. Se ainda não está claro que pode ser melhorado, vamos examinar os passos <tt>k=2</tt> e <tt>k=3</tt>:
  294. <br/>
  295. - <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/>
  296. - <tt>k=3</tt>: computa-se <tt>pot(x, 2*3)=pot(x, 6)</tt> e <tt>fat(2*3)=fat(6)</tt>.<br/>
  297. Mas, se usarmos uma variável <tt>pot1</tt> para manter a potência e <tt>fat1</tt> para manter o fatorial,
  298. <!--
  299. <i>x<sup>2k</sup></i> (<tt>pot(x, 2*k)</tt>) e
  300. <tt>fat1</tt> para manter <i>2k!</i> (<tt>fat(2*k)</tt>),
  301. -->
  302. então, ao final do passo <tt>k=2</tt>,
  303. <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>.
  304. <br/>
  305. Portanto, no passo <tt>k=3</tt>, bastaria fazer:
  306. <center>
  307. <tt>pot1 = pot1 * x * x</tt> &nbsp; e &nbsp; <tt>fat1 = fat1 * 5 * 6</tt>,
  308. </center>
  309. pois dessa forma, conseguiríamos
  310. <center><!-- &rArr; = implica -->
  311. <tt>pot1 &larr; pot1 * x * x &rArr; pot1 &larr; x<sup>4</sup> x<sup>2</sup> = x<sup>6</sup></tt> &nbsp; &nbsp; &nbsp; &nbsp; e
  312. <br/>
  313. <tt>fat1 &larr; fat1 * 5 * 6 &rArr; fat1 &larr; 4! * 5 * 6 = 6!</tt>.
  314. </center>
  315. </p>
  316. <p>
  317. Outro problema da solução da tabela 1 é que potência e fatorial crescem muito rápido
  318. (se <i>x &gt; 1</i>, mas se <i>x &lt; 1</i>, então decresce muito rápido).
  319. Isso também pode resultar ineficiência numérica.
  320. </p>
  321. <p>
  322. Portanto, esta solução 1 é muito ineficaz, tanto em termos de velocidade quanto de precisão.
  323. A discussão da razão de sua ineficácia já indica um caminho de melhoria, examinado a seguir.
  324. </p>
  325. <a name="ingenuo">
  326. <p class="secao">Uma segunda solução para a função <i>cosseno</i>: ainda ineficaz numericamente</p>
  327. </a>
  328. <p>
  329. Uma solução um pouco melhor, mas numericamente ainda ingênua, seria eliminar a grande ineficiência citada acima, a cada passo
  330. aproveitando a potência e o fatorial anterior.
  331. 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>,
  332. 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>:
  333. <center>
  334. <a name="solucaomedia">
  335. <i>Tab. 2. Uma implementação não tão ineficiente para a Série de Taylor que aproxima <i>cosseno</i>.</i>
  336. </a>
  337. <br/>
  338. <table class="tbCodeLinCol">
  339. <tr class="tbCodeLinColH"><th colspan="2">Algoritmo ineficiente para cômputo do cosseno</th></tr>
  340. <tr><th>C </th> <th>Python</th></tr>
  341. <tr valign="top"><td><table class="tbCode">
  342. <tr><td><pre><verm>float</verm> cossInef (<verm>float</verm> x) {
  343. <verm>float</verm> pot = 1, soma = 0;
  344. <verm>float</verm> termo0 = 2, termo = 1;
  345. <verm>int</verm> fat = 1, i = 1, sinal = 1;
  346. while (valor_absoluto(termo0-termo)>Lim) {
  347. soma = soma + termo;
  348. pot *= x * x; <cyan>// compute potencia 2i de x</cyan>
  349. fat *= (i+1) * (i+2); <cyan>// compute fatorial de 2i</cyan>
  350. termo0 = termo;
  351. termo = sinal * pot / fat;
  352. i += 2;
  353. sinal = -sinal;
  354. }
  355. return soma;
  356. }</pre></td></tr>
  357. </table></td>
  358. <td><table class="tbCode"><pre><verm>def</verm> cossInef (x) :
  359. pot = 1; soma = 0;
  360. termo0 = 2; termo = 1;
  361. fat = 1; i = 0; sinal = 1;
  362. while (valor_absoluto(termo0-termo)>Lim) :
  363. soma = soma + termo;
  364. pot *= x * x; <cyan># compute potencia 2i de x</cyan>
  365. fat *= (i+1) * (i+2); <cyan># compute fatorial de 2i</cyan>
  366. termo0 = termo;
  367. termo = sinal * pot / fat;
  368. i += 2;
  369. sinal = -sinal;
  370. return soma;</pre></td></tr>
  371. </table></td></tr>
  372. </table></center>
  373. </p>
  374. <p>
  375. Rodando este algoritmo e comparando-o com a implementação interna das linguagens <b>C</b> e <b>Python</b>, mesmo para valores
  376. 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
  377. acima, sua versão mais eficiente e a implementação interna da linguagem).
  378. Isso indica que o método acima <b>não</b> é eficiente.
  379. </p>
  380. <p>
  381. 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
  382. 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
  383. aproxima-se muito rápido é do zero).
  384. <p>
  385. </p>
  386. 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>.
  387. Assim, poderia-se tentar a cada passo multiplicar diretamente por esta "diferença" e não acumular a potência e o fatorial!
  388. </p>
  389. <a name="eficiente">
  390. <p class="secao">Uma solução eficiente para a função <i>cosseno</i></p>
  391. </a>
  392. <p>
  393. Usando esta ideia produz-se o algoritmo seguinte que é bem mais eficiente. Nele aproveitamos para ilustrar o uso
  394. de medição de tempo de execução tanto em <b>C</b> quanto em <b>Python</b>,
  395. <center>
  396. <a name="solucaoboa">
  397. <i>Tab. 3. Uma implementação bem eficiente para a Série de Taylor que aproxima <i>cosseno</i>.</i>
  398. </a>
  399. <br/>
  400. <table class="tbCodeLinCol">
  401. <tr class="tbCodeLinColH"><th colspan="2">Algoritmo eficiente para cômputo do cosseno - comparação com o ineficiente e o interno</th></tr>
  402. <tr><th>C </th> <th>Python</th></tr>
  403. <tr valign="top"><td><table class="tbCode">
  404. <tr><td><pre><cyan>// Leo^nidas O. Brandao - 2017/06/19</cyan>
  405. <cyan>// cos(x) = 0! x^0 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
  406. <cyan>// = 1 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
  407. <cyan>// gcc -lm -o introducao_eficiencia_algoritmos_cosseno introducao_eficiencia_algoritmos_cosseno.c</cyan>
  408. <incl1>#include</incl1> &lt;stdio.h&gt;
  409. <incl1>#include</incl1> &lt;math.h&gt; <cyan>// cos(x)</cyan>
  410. <incl1>#include</incl1> &lt;time.h&gt; <cyan>// clock_t, clock()</cyan>
  411. <def>#define</def> Lim 0.001
  412. <verm>float</verm> valor_absoluto (<verm>float</verm> x) {
  413. if (x&gt;=0) return x;
  414. return -x;
  415. }
  416. <cyan>// Cosseno implementado de modo ineficiente</cyan>
  417. <verm>float</verm> cossInef (<verm>float</verm> x) { ... } <cyan>// copiar aqui o codigo C do ineficiente acima</cyan>
  418. <cyan>// Cosseno implementado de modo mais eficiente</cyan>
  419. <verm>float</verm> cossEfic (<verm>float</verm> x) {
  420. <verm>float</verm> pot = 1, soma = 0;
  421. <verm>float</verm> termo0 = 2, termo = 1;
  422. <verm>int</verm> i = 0, sinal = 1;
  423. while (valor_absoluto(termo0-termo)&gt;Lim) {
  424. soma = soma + termo;
  425. termo0 = termo;
  426. termo *= -1 * x * x / ((i+1) * (i+2)); <cyan>// compute potencia 2i de x e fatorial de 2i</cyan>
  427. i += 2;
  428. }
  429. return soma;
  430. }
  431. <verm>int</verm> main (void) {
  432. <verm>float</verm> x, cosx, auxcI, auxcE;
  433. <verm>int</verm> i;
  434. clock_t tempo_inicial = clock(); <cyan>// "dispara o cronometro"...</cyan>
  435. <verd>printf</verd>(" x | ineficiente | eficiente | math cos(x) | dist inef. | dist efic.\n");
  436. x = 0.0;
  437. for (i=0; i&lt;10; i++) {
  438. cosx = cos(x);
  439. auxcI = cossInef(x);
  440. auxcE = cossEfic(x);
  441. <verd>printf</verd>(" %3.2f | %11.4f | %9.4f | %11.4f | %10.4f | %10.4f\n",
  442. x, auxcI, auxcE, cosx, valor_absoluto(auxcI-cosx), valor_absoluto(auxcE-cosx));
  443. x += 0.05;
  444. }
  445. clock_t tempo_final = clock();
  446. <verd>printf</verd>("Tempo em segundos: %f\n", (double)(tempo_final - tempo_inicial) / CLOCKS_PER_SEC);
  447. return 1;
  448. }</pre></td></tr>
  449. </table></td>
  450. <td><table class="tbCode"><pre><cyan># Leo^nidas O. Brandao - 2017/06/19</cyan>
  451. <cyan># cos(x) = 0! x^0 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
  452. <cyan># = 1 - x^2 /2! + x^4 /4! - x^6 /6! + x^8 /8! + ...</cyan>
  453. <cyan># Invocar execucao com linha de comando: python introducao_eficiencia_algoritmos_cosseno.py</cyan>
  454. <incl1>import</incl1> math; <cyan># para cos(x)</cyan>
  455. <incl1>import</incl1> time; <cyan># para tempo 'time.time()'</cyan>
  456. Lim = 0.001
  457. <verm>def</verm> valor_absoluto (x) :
  458. if (x>=0) : return x;
  459. return -x;
  460. <cyan># Cosseno implementado de modo mais eficiente</cyan>
  461. <verm>def</verm> cossInef (x) : <cyan># Copiar aqui a implementacao de cosseno ineficiente</cyan>
  462. ...
  463. <cyan># Cosseno implementado de modo mais eficiente</cyan>
  464. <verm>def</verm> cossEfic (x) :
  465. pot = 1; soma = 0;
  466. termo0 = 2; termo = 1;
  467. i = 0; sinal = 1;
  468. while (valor_absoluto(termo0-termo)>Lim) :
  469. soma = soma + termo;
  470. termo0 = termo;
  471. termo *= -1 * x * x / ((i+1) * (i+2)); <cyan># compute potencia 2i de x e fatorial de 2i</cyan>
  472. i += 2;
  473. return soma;
  474. <verm>def</verm> main () :
  475. tempo_inicial = time.time(); <cyan># "dispara o cronometro"...</cyan>
  476. <verd>print</verd>(" x | ineficiente | eficiente | math cos(x) | dist inef. | dist efic.");
  477. x = 0.0;
  478. for i in range(0, 10) :
  479. cosx = math.cos(x);
  480. auxcI = cossInef(x);
  481. auxcE = cossEfic(x);
  482. <verd>print</verd>(" %3.2f | %11.4f | %9.4f | %11.4f | %10.4f | %10.4f" %
  483. ( x, auxcI, auxcE, cosx, valor_absoluto(auxcI-cosx), valor_absoluto(auxcE-cosx) ));
  484. x += 0.05;
  485. tempo_final = time.time();
  486. <verd>print</verd>("Tempo em segundos: %f\n" % (tempo_final - tempo_inicial));
  487. main();</pre></td></tr>
  488. </table></td></tr>
  489. </table></center>
  490. </p>
  491. <p>
  492. 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).
  493. 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.
  494. Note que a medida
  495. que o <i>x</i> se afasta da origem, aparece mais erro, entretanto os erros são consistentemente maiores ao usar
  496. a implementação ingênua.
  497. <center>
  498. <i>Tab. 4. As saídas geradas pelos códigos ineficientes e eficientes (da tabela 3) para aproximar <i>cosseno</i>.</i>
  499. <pre> x | ineficiente | eficiente | math cos(x) | dist inef. | dist efic.
  500. 0.00 | 1.0000 | 1.0000 | 1.0000 | 0.0000 | 0.0000
  501. 0.05 | 1.0012 | 0.9988 | 0.9988 | 0.0025 | 0.0000
  502. 0.10 | 1.0050 | 0.9950 | 0.9950 | 0.0100 | 0.0000
  503. 0.15 | 1.0112 | 0.9888 | 0.9888 | 0.0225 | 0.0000
  504. 0.20 | 1.0199 | 0.9801 | 0.9801 | 0.0399 | 0.0000
  505. 0.25 | 1.0311 | 0.9689 | 0.9689 | 0.0622 | 0.0000
  506. 0.30 | 1.0447 | 0.9553 | 0.9553 | 0.0893 | 0.0000
  507. 0.35 | 1.0606 | 0.9394 | 0.9394 | 0.1213 | 0.0000
  508. 0.40 | 1.0789 | 0.9211 | 0.9211 | 0.1579 | 0.0000
  509. 0.45 | 1.0996 | 0.9004 | 0.9004 | 0.1991 | 0.0000</pre></center>
  510. </p>
  511. <p>
  512. 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>,
  513. Entretanto, os tempos de execução diferem,
  514. sendo significativamente menores utilizando a versão compilada (compilador <b>Gnu GCC</b>).
  515. Usando a mesma máquina (um PC com 2 processadores AMD Phenom II rodando Linux/Debian 8.8),
  516. os menores resultados de tempo de execução (dentre 100 testes) para o executável via <i>C</i> e o interpretado
  517. <i>Python</i>: <center><pre>
  518. C Python
  519. Tempo em segundos: 0.000079 0.000220</pre></center>
  520. </p>
  521. <p>
  522. Em novos testes, desta vez usando um computador de dois núcleos com processador <i>Intel Core i5-6400 CPU @ 2.70GHz</i>,
  523. com sistema <i>Debian GNU/Linux 9.12</i>, rodando 400 vezes, eliminando-se as linhas com comando de impressão
  524. (pois as saídas poderiam uniformizar os resultados por serem significativamente mais lentas),
  525. os resultados numéricos do executável (compilação <i>Gnu GCCC</i>) mostra um bastante superior ao interpretador <i>Python</i>.
  526. 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
  527. (em segundos):
  528. <center><pre>
  529. C Python
  530. Tempo medio: 0.000520 0.008276
  531. Tempo minimo: 0.000016 0.000076
  532. Tempo maximo: 0.000945 0.016031</pre></center>
  533. </p>
  534. <!--
  535. 2020/08/13 - /proc/cpuinfo = 2 x Intel(R) Core(TM) i5-6400 CPU @ 2.70GHz
  536. C
  537. -->
  538. <p>
  539. Este é apenas um resumo para apresentar o assunto de eficiência de algoritmos, seu aprofundamente demanda muito mais tempo.
  540. Por exemplo, no IME-USP existe um curso (usualmente de segundo ano) cujo objeto de estudo é precisamente
  541. discutir efeciência de algoritmos, o curso
  542. <a href="http://uspdigital.usp.br/jupiterweb/obterDisciplina?sgldis=MAC0122&verdis=2"
  543. title="ver ementa">MAC0122 - Princípios de Desenvolvimento de Algoritmos</a>.
  544. No programa de Verão do IME-USP temos o curso
  545. <a href="http://www.ime.usp.br/verao/index.php/turmas/apresentacao.php"
  546. title="ver lista de cursos do ultimo Verao">Tópicos de Programação</a>.
  547. </p>
  548. <p class="autoria">
  549. <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/>
  550. <a href="http://www.ime.usp.br/~leo" target="_blank" title="seguir para a página do LInE">http://line.ime.usp.br</a>
  551. </p>
  552. <p class="rodape">
  553. <b>Alterações</b>:<br/>
  554. 2020/08/15: novo formato, pequenas revisões<br/>
  555. 2020/08/13: ampla revisão (introdução nova, com imagem), novo formato;<br/>
  556. 2019/04/24: primeira versão
  557. </p>
  558. </div>