Compartilhar via


Dicas para melhorar o código de tempo crítico

Escrever código rápido, é necessário compreender todos os aspectos do seu aplicativo e como ele interage com o sistema. Este tópico sugere alternativas para algumas das técnicas de codificação mais óbvias para ajudá-lo a garantir que as partes críticas para a hora do seu código um desempenho satisfatório.

Para resumir, melhorar o código de tempo crítico requer que você:

  • Sabe quais partes do seu programa precisam ser rápido.

  • Sabe o tamanho e a velocidade do seu código.

  • Sabe o custo de novos recursos.

  • Sabe o mínimo necessário para realizar o trabalho de trabalho.

Para obter informações sobre o desempenho do seu código, você pode usar o monitor de desempenho (Perfmon. exe).

  • Acertos do cache e falhas de página

  • Classificação e pesquisa

  • MFC e bibliotecas de classe

  • Bibliotecas compartilhadas

  • Pilhas

  • Segmentos (Threads)

  • Conjunto de trabalho pequeno

Erros de cache e falhas de página

Perdeu os acertos do cache em ambos os cache interno e externo, bem como as falhas de página (Ir para o armazenamento secundário para dados e instruções de programa) lento o desempenho de um programa.

Um acerto de cache da CPU pode custar de seus ciclos de clock de 10 a 20 do programa. Um acerto de cache externo pode custar ciclos de clock de 20 a 40. Uma falha de página pode custar um milhão de ciclos de relógio (supondo que um processador que manipula 500 milhões de instruções por segundo e um tempo de 2 milissegundos para uma falha de página). Portanto, é no melhor interesse da execução do programa escrever código que reduzirá o número de acertos do cache perdidas e falhas de página.

Uma razão para programas lentas é que eles tenham mais falhas de página ou perder o cache com mais freqüência do que o necessário. Para evitar isso, é importante usar estruturas de dados com boa localidade de referência, o que significa manter os itens relacionados juntos. Às vezes, uma estrutura de dados parece ótima acaba por ser tão terrível por causa da má localidade de referência e, às vezes, o inverso é verdadeiro. Aqui estão dois exemplos:

  • Alocada dinamicamente listas vinculadas podem reduzir o desempenho do programa, porque quando você procurar um item ou quando você percorrer uma lista no final, cada link ignorado poderia perder o cache ou causar uma falha de página. Uma implementação de lista com base em matrizes simples pode realmente ser muito mais rápida devido cache melhor e menos falhas de página mesmo — permitindo o fato de que a matriz seria mais difícil a crescer, ele ainda pode ser rápido.

  • Tabelas de hash que usam alocadas dinamicamente listas vinculadas podem prejudicar o desempenho. Por extensão, tabelas de hash que usam o alocada dinamicamente listas vinculadas para armazenar seu conteúdo podem realizar consideravelmente pior. Na verdade, em última análise, uma pesquisa linear simples por meio de uma matriz pode realmente ser rápida (dependendo das circunstâncias). Tabelas de hash com base em array (os chamados "fechada hash") é uma implementação de muitas vezes negligenciado que freqüentemente tem um desempenho superior.

Classificação e pesquisa

A classificação é inerentemente demorado em comparação com muitas operações típicas. A melhor maneira de evitar a diminuição desnecessária é evitar a classificação em momentos críticos. Você poderá:

  • Adie a classificação até o momento não performance–critical.

  • Classificar os dados em um momento anterior, do não-performance–critical.

  • Classificar apenas a parte dos dados que realmente precisam de classificação.

Às vezes, você pode criar a lista em ordem classificada. Cuidado, pois se você precisar inserir dados em ordem classificada, você pode exigir uma estrutura de dados mais complicada com deficiente localidade de referência, levando a erros de cache e falhas de página. Não há nenhuma abordagem que funciona em todos os casos. Tente várias abordagens e mensurar as diferenças.

Aqui estão algumas dicas gerais para classificação:

  • Use uma classificação de ações para minimizar o bugs.

  • Qualquer trabalho que você pode fazer com antecedência para reduzir a complexidade da classificação é interessante. Se uma única passagem sobre os seus dados simplifica as comparações e reduz a classificação de O (n log n) para O (n), você certamente se sairá frente*.*

  • Pense sobre a localidade de referência do algoritmo de classificação e os dados que você espera que ele seja executado no.

Há menos alternativas para a procura de classificação. Se a pesquisa for urgente, uma pesquisa de tabela de hash ou de pesquisa binária quase sempre é melhor, mas com classificação, você deve ter a localidade em mente. Uma pesquisa linear através de uma matriz pequena pode ser mais rápido do que uma pesquisa binária através de uma estrutura de dados com muita ponteiros que causa falhas de página ou erros de cache.

MFC e bibliotecas de classe

O Microsoft Foundation Classes (MFC) podem simplificar bastante a escrever código. Ao escrever código de tempo crítico, você deve estar ciente da sobrecarga inerente em algumas das classes. Examine o código do MFC seu código de tempo crítico utiliza para ver se ele atende às suas necessidades de desempenho. A lista a seguir identifica as classes MFC e funções que você deve conhecer:

  • CStringMFC chama a biblioteca de tempo de execução c para alocar memória para um CString dinamicamente. Em geral, CString é tão eficiente quanto a quaisquer outros alocados dinamicamente string. Como ocorre com qualquer seqüência alocada dinamicamente, ele tem a sobrecarga de alocação dinâmica e release. Muitas vezes, uma simples char matriz na pilha pode servir o mesmo objetivo e é mais rápido. Não use um CString para armazenar uma seqüência constante. Use const char * em vez disso. Qualquer operação que você executar com um CString o objeto tem alguma sobrecarga. Usando a biblioteca de tempo de execução funções de cadeia de caracteres pode ser mais rápido.

  • CArrayA CArray fornece a flexibilidade que não de uma matriz normal, mas o programa talvez não precise que. Se você souber os limites específicos para a matriz, você pode usar uma matriz global de fixa em vez disso. Se você usar CArray, use CArray::SetSize para estabelecer o seu tamanho e especifique o número de elementos que ele cresce quando uma realocação é necessária. Caso contrário, a adição de elementos pode causar seu array sejam realocados com freqüência e copiados, o que é ineficiente e pode fragmentar em memória. Esteja ciente também de que se você inserir um item em uma matriz, CArray move os itens subseqüentes na memória e pode precisar crescer o array. Essas ações podem causar erros de cache e falhas de página. Se você examinar o código que usa o MFC, você poderá ver o que você pode escrever algo mais específico para seu cenário para melhorar o desempenho. Desde que CArray é um modelo, por exemplo, você poderá fornecer CArray especializações para tipos específicos.

  • CList   CList é uma lista duplamente vinculada, portanto, a inserção de elemento é rápida no topo, final e em uma posição conhecida (posição) na lista. Pesquisar um elemento por valor ou o índice requer uma pesquisa seqüencial, no entanto, o que pode ser lenta se a lista é longa. Se seu código não requer uma lista duplamente vinculada convém reconsiderar usando CList. Usar uma lista vinculada separada salva a sobrecarga de atualização de um ponteiro adicional para todas as operações, bem como a memória para que o ponteiro. A memória adicional não é ótima, mas é outra oportunidade para erros de cache ou falhas de página.

  • IsKindOfEsta função pode gerar muitas chamadas e acessar muita memória em áreas de dados diferentes, levando a localidade incorreta de referência. É útil para uma compilação de depuração (em uma chamada ASSERT, por exemplo), mas evite usá-lo em uma versão de compilação.

  • PreTranslateMessageUse PreTranslateMessage quando uma determinada árvore do windows precisa de aceleradores de teclado diferentes ou quando você deve inserir o tratamento de mensagens para a bomba de mensagem. PreTranslateMessageAltera as mensagens de despacho do MFC. Se você substituir PreTranslateMessage, portanto apenas no nível necessário. Por exemplo, não é necessário substituir CMainFrame::PreTranslateMessage se você estiver interessado somente em mensagens para os filhos de um determinado modo de exibição. Substituir PreTranslateMessage para o modo de exibição de classe em vez disso.

    Evita o caminho normal de despacho usando PreTranslateMessage para tratar qualquer mensagem enviada para a janela. Use os procedimentos de janela e mapas de mensagem do MFC para essa finalidade.

  • OnIdleEventos ociosos podem ocorrer às vezes você não espera, tal como entre WM_KEYDOWN e WM_KEYUP eventos. Timers podem ser uma maneira mais eficiente para acionar o seu código. Não force OnIdle a ser chamado repetidamente, gerando mensagens falsos ou retornando sempre TRUE a partir de uma substituição do OnIdle, que nunca permitiria que o thread de suspensão. Novamente, um thread separado ou um temporizador pode ser mais apropriado.

Bibliotecas compartilhadas

Reutilização de código é desejável. No entanto, se você for usar o código de outra pessoa, verifique se que você sabe exatamente o que ele faz nesses casos onde o desempenho é fundamental para você. A melhor maneira de entender isso está passando pelo código-fonte ou medir com ferramentas como o Monitor de desempenho ou de PView.

Pilhas

Use várias pilhas com discrição. Pilhas adicionais criadas com HeapCreate e HeapAlloc permitem gerenciar e, em seguida, descartar um conjunto relacionado de alocações. Não confirme muita memória. Se você estiver usando várias pilhas, Preste especial atenção à quantidade de memória está inicialmente comprometida.

Em vez de várias pilhas, você pode usar funções auxiliares para a interface entre o código e o heap padrão. Funções auxiliares facilitam a estratégias de alocação personalizada que podem melhorar o desempenho do seu aplicativo. Por exemplo, se você executar freqüentemente pequenas alocações, convém localizar essas alocações de uma parte da heap padrão. Você pode alocar um bloco grande de memória e, em seguida, usar uma função auxiliar para subalocar a partir desse bloco. Se você fizer isso, você não terá pilhas adicionais com a memória não utilizada porque a alocação está saindo de heap padrão.

Em alguns casos, no entanto, usar o heap padrão pode reduzir a localidade de referência. Use o Visualizador de processo, o Spy + + ou o Monitor de desempenho para medir os efeitos da movimentação de pilha para pilha de objetos.

Meça as pilhas para que você pode considerar cada alocação na pilha. Usar o tempo de execução c rotinas de heap de depuração para o ponto de verificação e despejo de pilha. 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 de alocações. Compare isso com o tamanho dos conjuntos de trabalho. Observe também o agrupamento de objetos relacionados de porte.

Você também pode usar os contadores de desempenho para monitorar o uso de memória.

Segmentos (Threads)

Para tarefas de plano de fundo, eficaz tratamento ocioso de eventos pode ser mais rápido do que usando segmentos. É mais fácil de entender a localidade de referência em um programa de thread único.

Uma boa regra prática é usar um thread somente se uma notificação do sistema operacional que você bloquear na está na raiz do trabalho de plano de fundo. Segmentos são a melhor solução nesse caso porque não é prático para bloquear um segmento principal em um evento.

Segmentos também apresentam problemas de comunicação. Você deve gerenciar o link de comunicação entre os segmentos, com uma lista de mensagens ou alocando e uso de memória compartilhada. Gerenciar o link de comunicação geralmente requer a sincronização para evitar condições de corrida e problemas de bloqueio. Essa complexidade pode ser facilmente ativado em erros e problemas de desempenho.

Para obter informações adicionais, consulte Processamento de Loop ocioso e Multithreading.

Conjunto de trabalho pequeno

Conjuntos de trabalho menores significam melhor localidade de referência, menos falhas de página e mais acertos do 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 superiores e inferiores do conjunto de trabalho, use SetProcessWorkingSetSize.

  • Para obter os limites superiores e inferiores do conjunto de trabalho, use GetProcessWorkingSetSize.

  • Para exibir o tamanho do conjunto de trabalho, use o Spy + +.

Consulte também

Referência

Otimização de seu código.