Partilhar via


Problemas de desempenho para um driver de miniporta 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 que é 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 miniporta WavePci devem resolver vários problemas de desempenho específicos para dispositivos de áudio. A discussão a seguir lida principalmente com questões 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 otimizações de desempenho, alguns antecedentes são necessários para entender os mecanismos WavePci para manutenção de fluxos.

Ao processar uma renderização de onda ou fluxo de captura, um dispositivo de áudio requer manutenção em intervalos regulares pelo driver da miniporta. Quando novos mapeamentos estão disponíveis para um fluxo, o driver adiciona esses mapeamentos à fila 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 miniporta fornece uma chamada de procedimento adiada (DPC) ou uma rotina de serviço de interrupção (ISR), dependendo se o intervalo é definido por um temporizador do sistema ou por interrupções controladas por DMA. Neste último caso, o hardware DMA normalmente dispara uma interrupção cada vez que termina a transferência de 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 um fluxo chamando o método IPortWavePci::Notifique . Esse método toma 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 sumidouro 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 invoca o método IMiniportWavePci::NewStream do driver de miniporta para obter um ponteiro para o grupo de serviço. O método RequestService do coletor de serviço é a rotina de serviço específica do driver de porta. Esta rotina faz o seguinte:

  • O método IMiniportWavePciStream::Service do controlador de miniporta é chamado.

  • Aciona qualquer nova posição pendente ou eventos de relógio no fluxo desde a última vez que a rotina de serviço foi executada.

Conforme discutido em KS Events, 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, caso em que o driver de porta configura seu próprio temporizador para marcar os intervalos entre chamadas para sua rotina de serviço.

Como o método NewStream, o método IMiniportWavePci::Init do driver de miniport também produz um ponteiro para um grupo de serviços. Após a chamada Init, o driver da porta adiciona o seu sumidouro de serviço ao grupo de serviços. Este terminal de serviço específico contém a rotina de serviço para o filtro no seu todo. (O parágrafo anterior descreve o ponto 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 da miniporta. A rotina de serviço é executada sempre que o DPC ou ISR chama Notify com o grupo de serviço do filtro. O método Init tem a opção de não fornecer um grupo de serviços, caso em que o driver de porta nunca chama sua rotina de serviço de filtro.

Interrupções de hardware

Alguns drivers de miniporta geram interrupções de hardware em excesso ou em quantidade insuficiente. Em alguns dispositivos de renderização WavePci com aceleração de hardware DirectSound, uma interrupção de hardware ocorre apenas quando o suprimento de mapeamentos está quase esgotado e o mecanismo de renderização corre o risco de esgotamento. Em outros dispositivos WavePci acelerados por hardware, uma interrupção de hardware ocorre em cada conclusão de mapeamento 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. O primeiro passo para melhorar o desempenho do motorista é reduzir o número de interrupções tanto quanto possível sem correr o risco de morrer de fome. Depois de eliminar interrupções desnecessárias, ganhos adicionais de desempenho podem ser alcançados projetando o ISR para ser executado de forma mais eficiente.

Em alguns drivers, os ISRs perdem tempo chamando o método Notify de um fluxo toda vez 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, liberar mapeamentos ou verificar 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 certificar-se de que quaisquer eventos pendentes em um fluxo são acionados quando o fluxo sai do estado RUN. Caso contrário, os eventos podem ser adiados ou perdidos. Este problema surge apenas durante transições RUN-to-PAUSE em sistemas operativos mais antigos do que o Microsoft Windows XP. No Windows XP e versões posteriores, o driver de porta sinaliza automaticamente quaisquer eventos de posição pendentes imediatamente quando um fluxo muda de estado de RUN para PAUSE. Nos sistemas operacionais mais antigos, no entanto, o driver de miniporta é responsável por acionar quaisquer eventos pendentes, fazendo uma chamada final para Notificar imediatamente após o fluxo ser pausado. Para obter mais informações, consulte Otimizações PAUSE/ACQUIRE abaixo.

Um driver de miniporta WavePci típico gerencia um único fluxo de reprodução do driver do sistema KMixer. A implementação atual do KMixer utiliza um mínimo de três IRPs de mapeamento para armazenar em buffer um fluxo de som. Cada IRP contém armazenamento buffer suficiente para cerca de 10 milissegundos de áudio. Se o driver de miniporta acionar uma interrupção de hardware cada vez que o controlador DMA terminar com o mapeamento final em um IRP, as interrupções devem ocorrer em intervalos de 10 milissegundos bastante regulares, o que é frequente o suficiente para evitar que a fila DMA passe fome.

DPCs do temporizador

Se um driver gerencia quaisquer fluxos DirectSound acelerados por hardware, ele deve usar um DPC de temporizador (consulte Objetos de temporizador e DPCs) em vez de interrupções de hardware orientadas por DMA. Equivalentemente, um dispositivo WavePci em uma placa PCI com um temporizador integrado pode usar uma interrupção de hardware orientada por temporizador em vez de um DPC.

No caso de um buffer DirectSound, todo o buffer pode ser conectado a um único IRP. Se o buffer for grande e o driver de miniport agendar uma interrupção de hardware somente quando atingir o final do buffer, interrupções sucessivas podem ocorrer tão espaçadas que a fila DMA fica sem recursos. 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 pode degradar o desempenho do sistema. Nessas circunstâncias, o driver de miniporta 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 agendado para ser executado em intervalos regulares gerados pelo temporizador.

Ao definir o intervalo do temporizador para 10 milissegundos, o intervalo entre as sucessivas execuções 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 KMixer além de fluxos DirectSound acelerados por hardware.

Quando o último fluxo sai do estado RUN, o driver de miniporta deve desativar o DPC do temporizador para evitar o desperdício de ciclos de CPU do sistema. Imediatamente após desativar o DPC, o driver deve certificar-se de que todos os eventos de relógio ou posição pendentes em fluxos que estavam em execução anteriormente são esvaziados. No Windows 98/Me e Windows 2000, o driver deve chamar Notify para disparar quaisquer eventos pendentes nos fluxos que estão sendo pausados. No Windows XP e versões posteriores, o sistema operacional dispara automaticamente quaisquer eventos pendentes quando um fluxo sai do estado RUN, sem exigir a intervenção do driver de miniporta.

Otimizações PAUSE/ACQUIRE

No Windows 98/Me e 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 miniporta, independentemente de o fluxo estar no estado RUN. Nesses sistemas operacionais, o método Service deve verificar se o fluxo está em execução antes de gastar tempo fazendo o trabalho real. (No entanto, se o DPC ou ISR do driver de miniporta já tiver sido otimizado para chamar Notify somente para fluxos em execução, adicionar essa verificação ao método Service pode ser redundante.)

No Windows XP e versões posteriores, essa otimização é desnecessária porque o método Notify chama o método Service somente 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 a ser emitidos do dispositivo (a melhor estimativa do controlador da amostra que se encontra atualmente no DAC). A posição de gravação é a posição no fluxo onde o próximo local seguro está disponível para o cliente gravar dados adicionais. Para WavePci, a suposição padrão é que o cursor de gravação está posicionado no final do último mapeamento solicitado. Se o driver da miniporta tiver adquirido um grande número de mapeamentos pendentes, o deslocamento entre o cursor de reprodução e o cursor de gravação pode ser muito grande — grande o suficiente para falhar em certos testes de posição de áudio do WHQL. No Windows XP e versões posteriores, a interface IPreFetchOffset resolve esses problemas.

O driver de miniporta usa IPreFetchOffset para especificar as características de pré-busca do hardware mestre de barramento, que são em grande parte determinadas pelo tamanho FIFO do 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 num mapeamento mesmo depois que o mapeamento foi entregue ao hardware, desde que o play cursor esteja suficientemente afastado do local em que os dados são gravados. (Esta instrução pressupõe que o driver não copia ou manipula os dados em mapeamentos.) Um deslocamento típico pode ser da ordem de 64 amostras, dependendo do projeto do motor. 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 preenche 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 manipule os dados nos mapeamentos, sempre que possível. Qualquer processamento de software de dados contidos em mapeamentos deve ser dividido em um filtro de software separado do driver de hardware. Ter um driver de hardware executando esse processamento reduz sua eficiência e cria problemas de latência.

Um driver de hardware deve se esforçar para ser transparente sobre suas capacidades reais de hardware. O driver nunca deve alegar fornecer suporte de hardware para uma transformação de dados que é realmente executada em software.

Primitivos de sincronização

É menos provável que um driver tenha problemas de bloqueio 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 paralisar enquanto aguarda outro thread ou recurso. Por exemplo, threads do driver podem usar as funções InterlockedXxx (por exemplo, consulte InterlockedIncrement) para coordenar os seus acessos a determinados recursos partilhados sem o risco de serem bloqueados.

Embora estas sejam técnicas poderosas, pode não ser possível remover com segurança todos os spin locks, mutexes e outras primitivas de sincronização de bloqueio do percurso de execução. Use as funções InterlockedXxx criteriosamente, com o conhecimento de que uma espera indefinida pode causar esgotamento de dados.

Acima de tudo, não crie primitivas de sincronização personalizadas. É provável que as primitivas internas do Windows (mutexes, bloqueios de rotação e assim por diante) sejam modificadas conforme necessário para oferecer suporte a novos recursos do agendador no futuro, e um driver que usa construções personalizadas é praticamente garantido que não funcionará no futuro.

IPinCount Interface

No Windows XP e versões posteriores, a interface IPinCount fornece uma maneira de um driver de miniporta contabilizar com mais precisão os recursos de hardware que são consumidos pela alocação de um pino. Ao chamar o método IPinCount::PinCount do driver de miniporta, o driver de porta faz o seguinte:

  • Expõe a contagem de pins atual do filtro (conforme mantida pelo driver da porta) para o controlador de miniporta.

  • Dá ao driver de miniporta a oportunidade de revisar as contagens de pinos para refletir dinamicamente a disponibilidade atual de 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 aumenta ou diminui a contagem de pinos disponíveis em um. Ao abrir um fluxo "pesado", no entanto, o driver de miniporta pode precisar diminuir a contagem de pinos disponíveis em dois, em vez de um, para indicar de forma mais precisa o número de pinos que podem ser criados com os recursos restantes.

O processo é invertido quando um fluxo de grande porte é 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 a partir dos recursos recém-liberados.