Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Escrever código rápido requer entender todos os aspetos do seu aplicativo e como ele interage com o sistema. Este artigo sugere alternativas a algumas das técnicas de codificação mais óbvias para ajudá-lo a garantir que as partes críticas de tempo do seu código tenham um desempenho satisfatório.
Para resumir, melhorar o código de tempo crítico requer que você:
Saiba quais partes do seu programa devem ser rápidas.
Saiba o tamanho e a velocidade do seu código.
Conheça o custo das novas funcionalidades.
Conheça o trabalho mínimo necessário para realizar o trabalho.
Para coletar informações sobre o desempenho do seu código, você pode usar o monitor de desempenho (perfmon.exe).
Secções deste artigo
Falhas de cache e falhas de página
Falhas nos acertos do cache, tanto no cache interno quanto no externo, assim como falhas de página (acessando o armazenamento secundário para instruções de programa e dados), afetam o desempenho de um programa.
Um acesso com sucesso no cache da CPU pode custar ao seu programa de 10 a 20 ciclos de clock. Um acerto de cache externo pode custar de 20 a 40 ciclos de relógio. Uma falha de página pode custar um milhão de ciclos de clock (assumindo um processador que lida com 500 milhões de instruções por segundo e um tempo de 2 milissegundos para uma falha de página). Portanto, é do melhor interesse da execução do programa escrever código que reduzirá o número de acessos de cache perdidos e falhas de página.
Uma razão para programas lentos é que eles têm mais falhas de página ou perdem o cache com mais frequência do que o necessário. Para evitar esse problema, é importante usar estruturas de dados com boa localidade de referência, o que significa manter as coisas relacionadas juntas. Às vezes, uma estrutura de dados que parece ótima acaba sendo horrível por causa da má localidade de referência, e às vezes o inverso é verdadeiro. Veja a seguir dois exemplos:
Listas vinculadas alocadas dinamicamente podem reduzir o desempenho do programa. Quando você pesquisa um item ou percorre uma lista até o final, cada link ignorado pode perder o cache ou causar uma falha de página. Uma implementação de lista baseada em matrizes simples pode ser mais rápida devido ao melhor cache e menos falhas de página. Mesmo que considerares que a matriz seria mais difícil de expandir, ainda pode ser mais rápido.
As tabelas de hash que usam listas vinculadas alocadas dinamicamente podem degradar o desempenho. Por extensão, as tabelas de hash que usam listas vinculadas alocadas dinamicamente para armazenar seu conteúdo podem ter um desempenho substancialmente pior. Na verdade, em última análise, uma simples pesquisa linear através de uma matriz pode realmente ser mais rápida (dependendo das circunstâncias). O uso de uma tabela de hash baseada em matriz (o chamado "hashing fechado") é uma implementação muitas vezes negligenciada que frequentemente tem desempenho superior.
Classificação e pesquisa
A classificação é inerentemente demorada em comparação com muitas operações típicas. A melhor maneira de evitar lentidão desnecessária é evitar ordenar em momentos críticos. Poderá ser capaz de:
Adiar a classificação até um momento não crítico de desempenho.
Classifique os dados em um momento anterior e não crítico de desempenho.
Classifique apenas a parte dos dados que realmente precisa de classificação.
Às vezes, você pode construir a lista em ordem ordenada. Tenha cuidado, porque se você precisar inserir dados em ordem ordenada, você pode precisar de uma estrutura de dados mais complicada com localização de referência pobre, levando a falhas de cache e de página. Não há uma abordagem que funcione em todos os casos. Experimente várias abordagens e meça as diferenças.
Aqui estão algumas dicas gerais para classificar:
Use um método de ordenação de inventário para minimizar erros.
Qualquer trabalho que você possa fazer de antemão para reduzir a complexidade do tipo vale a pena. Se uma única passagem pelos seus dados simplificar as comparações e reduzir a ordenação de O(n log n) para O(n), você quase certamente sairá na frente.
Pense na localidade de referência do algoritmo de classificação e nos dados nos quais você espera que ele seja executado.
Há menos alternativas para pesquisas do que para classificação. Se a pesquisa for crítica em termos de tempo, uma pesquisa binária ou uma pesquisa de tabela de hash é quase sempre melhor, mas, tal como acontece com a ordenação, deve ter em mente a localidade. Uma pesquisa linear através de uma pequena matriz pode ser mais rápida do que uma pesquisa binária através de uma estrutura de dados com muitos ponteiros que causa falhas de página ou falhas de cache.
MFC e bibliotecas de classes
O Microsoft Foundation Classes (MFC) pode simplificar muito a escrita de código. Ao escrever código crítico de tempo, você deve estar ciente da sobrecarga inerente a algumas das classes. Examine o código MFC que seu código de tempo crítico usa para ver se ele atende aos seus requisitos de desempenho. A lista a seguir identifica classes e funções MFC que você deve estar ciente:
CStringMFC chama a biblioteca de tempo de execução C para alocar dinamicamente memória para umCString. De um modo geral,CStringé tão eficiente quanto qualquer outra cadeia de caracteres alocada dinamicamente. Como com qualquer cadeia de caracteres alocada dinamicamente, ela tem a sobrecarga de alocação dinâmica e liberação. Muitas vezes, uma matriz simplescharna pilha pode servir o mesmo propósito e é mais rápida. Não use aCStringpara armazenar uma cadeia de caracteres constante. Utilizeconst char *em substituição. Qualquer operação executada com umCStringobjeto tem alguma sobrecarga. Usar as funções de cadeia de caracteres da biblioteca em tempo de execução pode ser mais rápido.CArrayACArrayfornece flexibilidade que uma matriz regular não tem, mas seu programa pode não precisar disso. Se você souber os limites específicos para a matriz, poderá usar uma matriz fixa global. Se você usarCArray, useCArray::SetSizepara estabelecer seu tamanho e especificar o número de elementos pelos quais ele cresce quando uma realocação é necessária. Caso contrário, a adição de elementos pode fazer com que sua matriz seja frequentemente realocada e copiada, o que é ineficiente e pode fragmentar a memória. Além disso, se você inserir um item em uma matriz,CArraymoverá os itens subsequentes na memória e talvez seja necessário aumentar a matriz. Essas ações podem causar falhas de cache e de página. Se você examinar o código que MFC usa, você pode ver que você pode escrever algo mais específico para o seu cenário para melhorar o desempenho. ComoCArrayé um modelo, por exemplo, você pode fornecerCArrayespecializações para tipos específicos.CListCListé uma lista duplamente vinculada, de modo que a inserção de elementos é rápida na cabeça, cauda e em uma posição conhecida (POSITION) na lista. No entanto, procurar um elemento por valor ou índice requer uma pesquisa sequencial, que pode ser lenta se a lista for longa. Se o seu código não exigir uma lista duplamente ligada, convém reconsiderar o uso deCList. O uso de uma lista vinculada individualmente salva a sobrecarga de atualizar outro ponteiro para todas as operações e a memória para esse ponteiro. A memória extra não é grande, mas representa outra possibilidade de falhas de cache ou de página.IsKindOfEsta função pode gerar muitas chamadas e pode aceder à memória em diferentes áreas de dados, o que pode resultar em uma má localidade de referência. É útil para uma compilação de depuração (numa chamada ASSERT, por exemplo), mas tente evitar usá-la numa compilação de lançamento.PreTranslateMessageUsePreTranslateMessagequando uma determinada árvore de janelas precisar de aceleradores de teclado diferentes ou quando você precisar inserir o tratamento de mensagens na bomba de mensagens.PreTranslateMessagealtera mensagens de envio MFC. Caso substituasPreTranslateMessage, faze-o apenas ao nível necessário. Por exemplo, não é necessário substituirCMainFrame::PreTranslateMessagese você estiver interessado apenas em mensagens destinadas a crianças de uma exibição específica. Em vez disso, substituaPreTranslateMessagepara a classe de exibição.Não contorne a rota normal de envio ao usar
PreTranslateMessagepara lidar com qualquer mensagem enviada para qualquer janela. Use procedimentos de janela e mapas de mensagens MFC para essa finalidade.OnIdleEventos ociosos podem ocorrer em momentos que você não espera, como entreWM_KEYDOWNeWM_KEYUPeventos. Os temporizadores podem ser uma maneira mais eficiente de acionar seu código. Não forceOnIdlea ser chamado repetidamente gerando mensagens falsas ou sempre retornandoTRUEde uma substituição doOnIdle, que nunca permitiria que seu thread dormisse. Novamente, um temporizador ou um thread separado pode ser mais apropriado.
Bibliotecas partilhadas
A reutilização do código é desejável. No entanto, se você vai usar o código de outra pessoa, certifique-se de saber exatamente o que ele faz nos casos em que o desempenho é crítico para você. A melhor maneira de entendê-lo é passando pelo código-fonte ou medindo com ferramentas como PView ou Performance Monitor.
Pilhas
Use várias pilhas com cautela. Pilhas adicionais criadas com HeapCreate e HeapAlloc permitem que você gerencie e, em seguida, elimine um conjunto relacionado de alocações. Não comprometa muita memória. Se você estiver usando várias pilhas, preste especial atenção à quantidade de memória inicialmente confirmada.
Em vez de vários heaps, você pode usar funções auxiliares para interagir entre seu código e o heap padrão. As funções auxiliares facilitam estratégias de alocação personalizadas que podem melhorar o desempenho do seu aplicativo. Por exemplo, se frequentemente efetua pequenas alocações, poderá querer concentrar estas alocações numa parte específica da pilha padrão. Você pode alocar um grande bloco de memória e, em seguida, usar uma função auxiliar para subalocar a partir desse bloco. Então não se terá múltiplos heaps com memória não utilizada, porque a alocação é proveniente do heap padrão.
Em alguns casos, no entanto, usar o heap padrão pode reduzir a localidade de referência. Utilize o Process Viewer, o Spy++ ou o Performance Monitor para medir os efeitos da movimentação de objetos entre pilhas.
Meça suas pilhas para que você possa contabilizar cada alocação na pilha. Utilize as rotinas de depuração de heap em tempo de execução C para verificar e despejar o heap. Você pode ler a saída em um programa de planilha como o Microsoft Excel e usar tabelas dinâmicas para exibir os resultados. Observe o número total, o tamanho e a distribuição das alocações. Compare estes resultados com o tamanho dos conjuntos de trabalho. Observe também o agrupamento de objetos de tamanho relacionado.
Você também pode usar os contadores de desempenho para monitorar o uso de memória.
Fios
Para tarefas em segundo plano, a manipulação eficaz de eventos em estado ocioso pode ser mais rápida do que o uso de threads. É mais fácil entender localidade de referência num programa monothread.
Uma boa regra geral é usar um thread somente se uma notificação do sistema operacional que você bloqueia estiver na raiz do trabalho em segundo plano. Os threads são a melhor solução nesse caso, porque é impraticável bloquear um thread principal em um evento.
Os threads também apresentam problemas de comunicação. Você deve gerenciar o link de comunicação entre seus threads, com uma lista de mensagens ou alocando e usando memória compartilhada. Gerir o link de comunicação geralmente requer sincronização para evitar condições de concorrência e problemas de impasse. Essa complexidade pode facilmente se transformar em bugs e problemas de desempenho.
Para obter mais informações, consulte Processamento de loop ocioso e multithreading.
Pequeno conjunto de trabalho
Conjuntos de trabalho menores significam melhor localidade de referência, menos falhas de página e mais acessos de cache. O conjunto de trabalho do processo é a métrica mais próxima que o sistema operacional fornece diretamente para medir a localidade de referência.
Para definir os limites superior e inferior do conjunto de trabalho, use
SetProcessWorkingSetSize.Para obter os limites superior e inferior do conjunto de trabalho, use
GetProcessWorkingSetSize.Para visualizar o tamanho do conjunto de trabalho, use Spy++.