Problemas de desempenho para um driver de miniporto WavePci
O impacto no desempenho de um driver de áudio no sistema pode ser significativamente reduzido seguindo estes princípios gerais:
Minimize o código executado durante a operação normal.
Execute o código somente quando necessário.
Considere o consumo total de recursos do sistema (não apenas o carregamento da CPU).
Otimize o código para velocidade e tamanho.
Além disso, os drivers de miniporto WavePci devem resolver vários problemas de desempenho específicos para dispositivos de áudio. A discussão a seguir trata principalmente de problemas de renderização de áudio, embora algumas das técnicas sugeridas também se apliquem à captura de áudio.
Mecanismos de manutenção de fluxo
Antes de discutir as otimizações de desempenho, alguns planos de fundo são necessários para entender os mecanismos WavePci para fluxos de manutenção.
Ao processar uma renderização de onda ou um fluxo de captura, um dispositivo de áudio requer manutenção em intervalos regulares pelo driver de miniporte. Quando novos mapeamentos estão disponíveis para um fluxo, o driver adiciona esses mapeamentos à fila de DMA do fluxo. O driver também remove da fila todos os mapeamentos que já foram processados. Para obter informações sobre mapeamentos, consulte Latência WavePci.
Para executar a manutenção, o driver de miniporto fornece uma DPC (chamada de procedimento adiado) ou isr (rotina de serviço de interrupção), dependendo se o intervalo é definido por um temporizador do sistema ou por interrupções controladas por DMA. No último caso, o hardware de DMA normalmente dispara uma interrupção sempre que termina de transferir alguma quantidade de dados de fluxo.
Cada vez que o DPC ou ISR é executado, ele determina quais fluxos exigem manutenção. O DPC ou ISR atende a um fluxo chamando o método IPortWavePci::Notify . Esse método usa como parâmetro de chamada o grupo de serviços do fluxo, que é um objeto do tipo IServiceGroup. O método Notify chama o método RequestService do grupo de serviços (consulte IServiceSink::RequestService).
Um objeto de grupo de serviços contém um grupo de coletores de serviço, que são objetos do tipo IServiceSink. IServiceGroup é derivado de IServiceSink e ambas as interfaces têm métodos RequestService . Quando o método Notify chama o método RequestService do grupo de serviços, o grupo de serviços responde chamando o método RequestService em cada coletor de serviço no grupo.
O grupo de serviços de um fluxo contém pelo menos um coletor de serviço, que o driver de porta adiciona ao grupo de serviços imediatamente após a criação do fluxo. O driver de porta chama o método IMiniportWavePci::NewStream do driver de miniport para obter um ponteiro para o grupo de serviços. O método RequestService do coletor de serviço é a rotina de serviço específica do fluxo do driver de porta. Essa rotina faz o seguinte:
Chama o método IMiniportWavePciStream::Service do driver de miniport.
Dispara qualquer evento de posição ou relógio recém-pendente no fluxo desde a última vez em que a rotina de serviço foi executada.
Conforme discutido em Eventos KS, os clientes podem se registrar para serem notificados quando um fluxo atinge uma posição específica ou quando um relógio atinge um carimbo de data/hora específico. O método NewStream tem a opção de não fornecer um grupo de serviços, nesse caso, o driver de porta configura seu próprio temporizador para marcar os intervalos entre chamadas para sua rotina de serviço.
Assim como o método NewStream , o método IMiniportWavePci::Init do driver de miniport também gera um ponteiro para um grupo de serviços. Após a chamada de Inicialização , o driver de porta adiciona seu coletor de serviço ao grupo de serviços. Esse coletor de serviço específico contém a rotina de serviço do filtro como um todo. (O parágrafo anterior descreve o coletor de serviço para o fluxo associado a um pino no filtro.) Essa rotina de serviço chama o método IMiniportWavePci::Service do driver de miniport. A rotina de serviço é executada sempre que o DPC ou ISR chama Notify com o grupo de serviços do filtro. O método Init tem a opção de não fornecer um grupo de serviços, nesse caso, o driver de porta nunca chama sua rotina de serviço de filtro.
Interrupções de hardware
Alguns drivers de miniporto geram muitas ou não interrupções de hardware suficientes. Em alguns dispositivos de renderização WavePci com aceleração de hardware DirectSound, uma interrupção de hardware ocorre somente quando o fornecimento de mapeamentos está quase esgotado e o mecanismo de renderização corre o risco de passar fome. Em outros dispositivos WavePci acelerados por hardware, ocorre uma interrupção de hardware em cada conclusão de mapeamento individual ou em algum outro intervalo relativamente pequeno. Nesse caso, o ISR frequentemente descobre que tem pouco a fazer, mas cada interrupção ainda consome recursos do sistema com trocas de registro e recarregamentos de cache. A primeira etapa para melhorar o desempenho do driver é reduzir o número de interrupções o máximo possível sem correr o risco de passar fome. Depois de eliminar interrupções desnecessárias, ganhos de desempenho adicionais podem ser obtidos projetando o ISR para ser executado com mais eficiência.
Em alguns drivers, os ISRs perdem tempo chamando o método Notify de um fluxo sempre que ocorre uma interrupção de hardware, independentemente de o fluxo estar realmente em execução. Se nenhum fluxo estiver no estado RUN, o DMA estará inativo e qualquer tempo gasto tentando adquirir mapeamentos, mapeamentos de versão ou marcar para novos eventos em qualquer fluxo será desperdiçado. Em um driver eficiente, o ISR verifica se um fluxo está em execução antes de chamar o método Notify do fluxo.
No entanto, um driver com esse tipo de ISR precisa garantir que todos os eventos pendentes em um fluxo sejam disparados quando o fluxo sair do estado RUN. Caso contrário, os eventos poderão ser atrasados ou perdidos. Esse problema surge somente durante transições RUN-to-PAUSE em sistemas operacionais mais antigos do que o Microsoft Windows XP. No Windows XP e posteriores, o driver de porta sinaliza automaticamente todos os eventos de posição pendentes imediatamente quando um fluxo altera o estado de EXECUTAR para PAUSAr. No entanto, nos sistemas operacionais mais antigos, o driver de miniporto é responsável por disparar eventos pendentes fazendo uma chamada final para Notificar imediatamente após a pausa do fluxo. Para obter mais informações, consulte Pause/ACQUIRE Optimizations abaixo.
Um driver de miniporto WavePci típico gerencia um único fluxo de reprodução do driver do sistema KMixer. A implementação atual do KMixer usa um mínimo de três IRPs de mapeamento para armazenar em buffer um fluxo de reprodução. Cada IRP contém armazenamento de buffer suficiente para cerca de 10 milissegundos de áudio. Se o driver de miniporto disparar uma interrupção de hardware sempre que o controlador DMA terminar com o mapeamento final em um IRP, as interrupções deverão ocorrer em intervalos regulares de 10 milissegundos, o que é frequente o suficiente para impedir que a fila de DMA passe fome.
DPCs do temporizador
Se um driver gerenciar fluxos DirectSound acelerados por hardware, ele deverá usar um DPC de temporizador (consulte Objetos de Temporizador e DPCs) em vez de interrupções de hardware controladas por DMA. Equivalentemente, um dispositivo WavePci em um cartão PCI com um temporizador a bordo pode usar uma interrupção de hardware controlada por temporizador em vez de um DPC.
No caso de um buffer DirectSound, todo o buffer pode ser anexado a um único IRP. Se o buffer for grande e o driver de miniporto agendar uma interrupção de hardware somente quando chegar ao final do buffer, interrupções sucessivas poderão ocorrer tão distantes que a fila de DMA passa fome. Além disso, se o driver estiver gerenciando um grande número de fluxos DirectSound acelerados por hardware e cada fluxo gerar suas próprias interrupções, o impacto cumulativo de todas as interrupções poderá prejudicar o desempenho do sistema. Nessas circunstâncias, o driver de miniporto deve evitar o uso de interrupções de hardware para agendar a manutenção de fluxos individuais. Em vez disso, ele deve atender a todos os fluxos em um único DPC que está agendado para ser executado em intervalos gerados pelo temporizador regular.
Ao definir o intervalo de temporizador como 10 milissegundos, o intervalo entre execuções sucessivas de DPC é semelhante ao descrito anteriormente para a interrupção de hardware no caso de um único fluxo de reprodução do KMixer. Assim, o DPC pode lidar com o fluxo de reprodução do KMixer, além de fluxos DirectSound acelerados por hardware.
Quando o último fluxo sair do estado RUN, o driver de miniporto deverá desabilitar o DPC do temporizador para evitar o perda de ciclos de CPU do sistema. Imediatamente após desabilitar o DPC, o driver deve garantir que todos os eventos de relógio ou posição pendentes em fluxos em execução anteriormente sejam liberados. No Windows 98/Me e no Windows 2000, o driver deve chamar Notify para disparar eventos pendentes nos fluxos que estão sendo pausados. No Windows XP e posteriores, o sistema operacional dispara todos os eventos pendentes automaticamente quando um fluxo sai do estado RUN, sem a necessidade de intervenção do driver de miniporte.
Otimizações PAUSE/ACQUIRE
No Windows 98/Me e no Windows 2000, a rotina de serviço de fluxo do driver de porta WavePci (o método RequestService ) sempre gera uma chamada para o método IMiniportWavePciStream::Service do driver de miniport, independentemente de o fluxo estiver no estado RUN. Nesses sistemas operacionais, o método Service deve marcar se o fluxo está em execução antes de passar um tempo fazendo o trabalho real. (No entanto, se o DPC ou ISR do driver de miniporte já tiver sido otimizado para chamar Notificar somente para fluxos em execução, adicionar esse marcar ao método Service poderá ser redundante.)
No Windows XP e posteriores, essa otimização é desnecessária porque o método Notify chama o método Service apenas em fluxos em execução.
Usando a interface IPreFetchOffset
Os usuários do DirectSound estão familiarizados com os conceitos duplos do cursor de reprodução e do cursor de gravação. O cursor de reprodução indica a posição no fluxo dos dados que estão sendo emitidos do dispositivo (a melhor estimativa do driver da amostra atualmente no DAC). A posição de gravação é a posição no fluxo do próximo local seguro para o cliente gravar dados adicionais. Para WavePci, a suposição padrão é que o cursor de gravação esteja posicionado no final do último mapeamento solicitado. Se o driver de miniporto tiver adquirido um grande número de mapeamentos pendentes, o deslocamento entre o cursor de reprodução e o cursor de gravação poderá ser muito grande, grande o suficiente para falhar em determinados testes de posição de áudio WHQL. No Windows XP e posterior, a interface IPreFetchOffset resolve esses problemas.
O driver de miniporto usa IPreFetchOffset para especificar as características de pré-busca do hardware de master de barramento, que são determinadas em grande parte pelo tamanho do FIFO de hardware. O subsistema de áudio usa esses dados para definir um deslocamento constante entre o cursor de reprodução e o cursor de gravação. Esse deslocamento constante, que pode ser significativamente menor do que o deslocamento padrão, aproveita o fato de que os dados podem ser gravados em um mapeamento mesmo após o mapeamento ter sido entregue ao hardware, desde que o cursor de reprodução esteja longe o suficiente do local em que os dados são gravados. (Essa instrução pressupõe que o driver não copie ou manipule os dados em mapeamentos.) Um deslocamento típico pode estar na ordem de 64 amostras, dependendo do design do mecanismo. Com um deslocamento tão pequeno, um driver WavePci pode ser totalmente responsivo e funcional enquanto ainda solicita um grande número de mapeamentos.
Observe que o DirectSound atualmente armazena o cursor de gravação de um pino acelerado por hardware em 10 milissegundos.
Para obter mais informações, consulte Deslocamentos de pré-busca.
Processando dados em mapeamentos
Evite que o driver de hardware toque os dados nos mapeamentos, se possível. Qualquer processamento de software de dados contidos em mapeamentos deve ser dividido em um filtro de software separado do driver de hardware. Fazer com que um driver de hardware execute esse processamento reduz sua eficiência e cria problemas de latência.
Um driver de hardware deve se esforçar para ser transparente sobre seus recursos reais de hardware. O driver nunca deve alegar fornecer suporte de hardware para uma transformação de dados que seja realmente executada no software.
Primitivos de sincronização
É menos provável que um driver tenha problemas de deadlock ou desempenho agora e no futuro se seu código for projetado para evitar ser bloqueado sempre que possível. Especificamente, o thread de execução de um driver deve se esforçar para ser executado até a conclusão sem o risco de parar enquanto aguarda outro thread ou recurso. Por exemplo, os threads de driver podem usar as funçõesXxx interlocked (por exemplo, consulte InterlockedIncrement) para coordenar seus acessos a determinados recursos compartilhados sem o risco de serem bloqueados.
Embora sejam técnicas poderosas, talvez você não consiga remover com segurança todos os bloqueios de rotação, mutexes e outros primitivos de sincronização de bloqueio do caminho de execução. Use as funçõesXxx interligadas criteriosamente, com o conhecimento de que uma espera indefinida pode causar falta de dados.
Acima de tudo, não crie primitivos de sincronização personalizados. Os primitivos internos do Windows (mutexes, bloqueios de rotação e assim por diante) provavelmente serão modificados conforme necessário para dar suporte a novos recursos do agendador no futuro, e um driver que usa constructos personalizados é praticamente garantido para não funcionar no futuro.
IPinCount Interface
No Windows XP e posteriores, a interface IPinCount fornece uma maneira de um driver de miniporto levar em conta com mais precisão os recursos de hardware que são consumidos alocando um pino. Ao chamar o método IPinCount::P inCount do driver de miniport, o driver de porta faz o seguinte:
Expõe as contagens de pinos atuais do filtro (conforme mantido pelo driver de porta) para o driver de miniporto.
Dá ao driver de miniporto a oportunidade de revisar as contagens de pinos para refletir dinamicamente a disponibilidade atual dos recursos de hardware.
Para alguns dispositivos de áudio, fluxos de onda com atributos diferentes (3D, estéreo/mono e assim por diante) também podem ter "pesos" diferentes em termos de quantos recursos de hardware consomem. Ao abrir ou fechar um fluxo "leve", o driver incrementa ou diminui a contagem de pinos disponíveis por um. No entanto, ao abrir um fluxo "pesado", o driver de miniporto pode precisar diminuir a contagem de pinos disponível em dois em vez de por um para indicar com mais precisão o número de pinos que podem ser criados com os recursos restantes.
O processo é invertido quando um fluxo de pesos pesados é fechado. A contagem de pinos disponíveis pode aumentar em mais de um para refletir o fato de que dois ou mais fluxos leves podem ser criados com base nos recursos recém-liberados.