270 likes | 344 Views
Técnicas de Programação: Profiling de Programas. Jacques Philippe Sauvé. Introdução. Problemas freqüentes de programas: Lentidão devido a consumo de CPU Alocação excessiva de memória (memory leak) Deadlocks entre threads
E N D
Técnicas de Programação: Profiling de Programas Jacques Philippe Sauvé J P Sauvé - DSC/UFPb
Introdução • Problemas freqüentes de programas: • Lentidão devido a consumo de CPU • Alocação excessiva de memória (memory leak) • Deadlocks entre threads • Este seminário mostra como resolver esses problemas usando ferramentas chamadas perfiladores J P Sauvé - DSC/UFPb
Programação • Visão geral de profiling • (5 minutos) • Profiling para descobrir gargalos de CPU • (45 minutos) • Profiling para descobrir memory leaks • (20 minutos) • Profiling para descobrir deadlocks • (30 (minutos) J P Sauvé - DSC/UFPb
Visão geral • Os três problemas relacionados são freqüentes • Não se deve resolvê-los no chute! • A chave éinstrumentar o programa para que ele mesmo nos diga o que está errado! MEM CPU MEM Instrumentação Deadlock Deadlock CPU J P Sauvé - DSC/UFPb
Vocabulário • Perfilador: programa que fornece informação sobre os detalhes da execução de um outro programa • hprof: Perfilador da JVM padrão • java –Xrunhprof:[opções] classe J P Sauvé - DSC/UFPb
Profiling de CPU – Número Primos • O programa imprime o número de primos menores que um certo número máximo • Execute o programa: • java Primo1 100001229481 milissegundos J P Sauvé - DSC/UFPb
Primeira Versão public class Primo1 { public static boolean primo(int n) { for(int i = 2; i < n; i++) { if(n % i == 0) { return false; } } return true; } public static void main(String[] args) { int max = Integer.parseInt(args[0]); int númeroPrimos = 0; long horaInicial = System.currentTimeMillis(); for(int i = 2; i <= max; i++) { if(primo(i)) { númeroPrimos++; } } System.out.println(númeroPrimos); System.out.println((System.currentTimeMillis() – horaInicial)+" milissegundos"); } } J P Sauvé - DSC/UFPb
Primeira Versão: Tempo de Execução • Max 10000: 0,481 segundos • Max 100000: 36,392 seg. • Max 1000000: ? • (não tive paciência de esperar) • O programa é obviamente CPU-bound J P Sauvé - DSC/UFPb
Primeira Versão: Como Melhorar? • Vamos conseguir melhorias de pelo menos 200 vezes nesse programa • Como fazer? • Não chute ... Instrumente! J P Sauvé - DSC/UFPb
Primeira versão: Profiling • Comando: • java -Xrunhprof:cpu=times,depth=10 Primo5 100000 • Cpu=times pede a contagem de chamadas de métodos • Depth=10 pede no máximo profundidade 10 no stack backtrace J P Sauvé - DSC/UFPb
Resultado do Profiling • Examine o arquivo java.hprof.txt rank self accum count trace method 1 98.44% 98.44% 99999 8 Primo1.primo 2 1.28% 99.72% 1 6 Primo1.main 3 0.03% 99.75% 1 13 java.security.Policy$1.run ... • Nosso problema é obviamente tempo demais sendo gasto no método primo() • Primeira lição: Para diminuir o gargalo de primo, pode-se: • Chamar o método menos • Tornar o método mais rápido • Aqui, decidimos não “fuçar” main(): concentre-se em primo() J P Sauvé - DSC/UFPb
Segunda versão • Como rodar primo() mais rapidamente? • Que tal rodar menos no laço? • Basta testar fatores até sqrt(n) public class Primo2 { public static int raiz(int n) { return (int)Math.sqrt((double)n); } public static boolean primo(int n) { for(int i = 2; i <= raiz(n); i++) { if(n % i == 0) { return false; } } return true; } ... } J P Sauvé - DSC/UFPb
Segunda versão - profiling • É mais rápida que a primeira: • P1: 10K/100K/1M: 0,481/36,392/? • P2: 10K/100K/1M: 0,060/1,132/27,189 • Onde está o gargalo? rank self accum count trace method 1 57.10% 57.10% 2755286 15 Primo2.raiz 2 40.99% 98.09% 99999 17 Primo2.primo 3 1.36% 99.45% 1 12 Primo2.main • Veja quantas vezes raiz() foi chamada! J P Sauvé - DSC/UFPb
Terceira versão: chame sqrt menos public static boolean primo(int n) { int limite = raiz(n); for(int i = 2; i <= limite; i++) { if(n % i == 0) { return false; } } return true; } • Os tempos passam para: • P3: 10K/100K/1M: 0,020/0,311/6,720 • Já está 100 vezes mais rápido! J P Sauvé - DSC/UFPb
Terceira versão: perfil rank self accum count trace method 1 50.02% 50.02% 99999 20 Primo3.primo 2 25.37% 75.38% 99999 14 Primo3.raiz 3 17.48% 92.86% 1 10 Primo3.main • raiz() caiu muito • Temos que melhorar primo ainda J P Sauvé - DSC/UFPb
Quarta versão: tentativa • Com casos especiais, poderemos reduzir o tempo? public static boolean primo(int n) { if(n % 2 == 0) return false; if(n % 3 == 0) return false; if(n % 5 == 0) return false; int limite = raiz(n); for(int i = 7; i <= limite; i += 2) { if(n % i == 0) return false; } return true; } J P Sauvé - DSC/UFPb
Quarta versão: bug • A saída de Primo1 10000 é1129 • A saída de Primo4 10000 é1126 • O tempo melhorou: • P3: 10K/100K/1M: 0,020/0,311/6,720 • P4: 10K/100K/1M: 0,020/0,151/2,924 • Tem um bug! • Mesmo com o programa mais rápido, uma regra básica é mantê-lo funcionando enquanto se o deixa mais rápido! • Cadê o bug? J P Sauvé - DSC/UFPb
Quinta versão: outra tentativa • Consertamos o bug e retiramos sqrt() public static boolean primo(int n) { if(n % 2 == 0) return n == 2; if(n % 3 == 0) return n == 3; if(n % 5 == 0) return n == 5; for(int i = 7; i * i <= n; i += 2) { if(n % i == 0) return false; } return true; } J P Sauvé - DSC/UFPb
Quinto programa: tempo e profile • Tempo não melhorou: • P4: 10K/100K/1M: 0,020/0,151/2,924 • P5: 10K/100K/1M: 0,020/0,151/3,285 • Não entendi bem por quê! • Último perfil: rank self accum count trace method 1 53.77% 53.77% 99999 17 Primo5.primo 2 34.67% 88.44% 1 9 Primo5.main J P Sauvé - DSC/UFPb
Profiling para Achar Memory Leaks • Memory Leaks são bugs de programas que esquecem de liberar memória alocada • O programa fica cada vez maior • Problema muito sério para programas servidores que ficam sempre no ar • Menos freqüente para Java do que C/C++ devido ao garbage collection • Em Java, há memory leak se você continuar a referenciar um objeto J P Sauvé - DSC/UFPb
O Programa MemoryLeak.java • Veja aqui • 10000 objetos MemoryConsumer de 1KBytes cada são alocados • Execute o programa: • java -Xrunhprof:heap=sites,depth=10 MemoryLeak J P Sauvé - DSC/UFPb
O perfil de MemoryLeak.java percent live alloc'ed stack class rank self accum bytes objs bytes objs trace name 1 88.67% 88.67% 2600000 10000 2600000 10000 185 [B 2 5.10% 93.77% 149524 157 149524 157 1 [I 3 1.36% 95.14% 40000 10000 40000 10000 175 MemoryConsumer 4 0.72% 95.86% 21140 1057 200000 10000 169 [C 5 0.66% 96.52% 19466 741 19626 754 1 [C • [B significa “Array de bytes”, etc. • O que causou a alocação toda? • Veja o trace 185: vem de main que chama o construtor de MemoryConsumer TRACE 185: MemoryConsumer.<init>(MemoryLeak.java:36) MemoryLeak.main(MemoryLeak.java:16) J P Sauvé - DSC/UFPb
Um programa de deadlock • Veja o programa de deadlock aqui • Em ClassicDeadlock: • Dois objetos equivalentes são criados (left e right) • Dois threads pegam locks nesses objetos numa ordem particular • Execute o programa • java -Xdebug ClassicDeadlock • Eventualmente, um deadlock vai ocorrer em que cada thread espera por um lock que o outro thread detém • Mate o programa com CTRL-\ (UNIX) ou CTRL-BREAK (Windows) J P Sauvé - DSC/UFPb
Diagnóstico de Deadlocks Found one Java-level deadlock: ============================= "RightToLeft": waiting to lock monitor 0x8092e34 (object 0x44493a10, a SynchronizedObject), which is held by "LeftToRight" "LeftToRight": waiting to lock monitor 0x8092dfc (object 0x44493a20, a SynchronizedObject), which is held by "RightToLeft" • O deadlock pode ser identificado, acima J P Sauvé - DSC/UFPb
Diagnóstico de Deadlocks Java stack information for the threads listed above: =================================================== "RightToLeft": at SynchronizedObject.synchronizedMethod(ClassicDeadlock.java:30) - waiting to lock <0x44493a10> (a SynchronizedObject) at RightToLeftGrabber.grabRightToLeft(ClassicDeadlock.java:68) - locked <0x44493a20> (a SynchronizedObject) at RightToLeftGrabber.run(ClassicDeadlock.java:62) "LeftToRight": at SynchronizedObject.synchronizedMethod(ClassicDeadlock.java:30) - waiting to lock <0x44493a20> (a SynchronizedObject) at LeftToRightGrabber.grabLeftToRight(ClassicDeadlock.java:88) - locked <0x44493a10> (a SynchronizedObject) at LeftToRightGrabber.run(ClassicDeadlock.java:82) Found 1 deadlock. J P Sauvé - DSC/UFPb
Resumo • Aprendemos que: • Três problemas freqüentes de execução de programas são: • Excesso de consumo de CPU • Memory leak • Deadlocks • É importante usar ferramentas adequadas para diagnosticar esses problemas • Uma boa ferramenta é um “perfilador” • Ele adquire estatísticas do programa durante sua execução e apresenta os resultados de forma legível (mais ou menos!) J P Sauvé - DSC/UFPb
Onde obter mais informações • Livros • Programming Pearls, Bentley • More Programming Pearls, Bentley • Artigo na Web • Diagnose Common Runtime Problems with Hprof, Pierce, JavaWorld, dezembro 2001 J P Sauvé - DSC/UFPb