9. Sessão bônus de QA por Allen Briggs

9.1. O que é “o algoritmo de intercalação” ao qual você se refere em sua listagem dos males dos arranjos de swap do FreeBSD 3.X?
9.2. Como a separação de páginas limpas e sujas (inativas) está relacionada à situação em que você vê baixas contagens de filas de cache e altas contagens de filas ativas no systat -vm? As estatísticas do systat rolam as páginas ativa e inativas juntas para a contagem de filas ativas?
9.3. No exemplo ls(1) / vmstat 1, algumas falhas de página não seriam falhas de página de dados (COW do arquivo executável para a página privada)? Ou seja, eu esperaria que as falhas de página fossem um preenchimento com zero e alguns dados do programa. Ou você está sugerindo que o FreeBSD faz pré-COW para os dados do programa?
9.4. Em sua seção sobre otimizações de tabela de páginas, você pode dar um pouco mais de detalhes sobre pv_entry e vm_page (ou vm_page deveria ser vm_pmap- como em 4.4, cf. pp. 180-181 of McKusick, Bostic, Karel, Quarterman)? Especificamente, que tipo de operação/reação exigiria a varredura dos mapeamentos?
9.5. Finalmente, na seção de page coloring, pode ser útil descrever um pouco mais o que você quer dizer aqui. Eu não segui bem isso.

9.1.

O que é o algoritmo de intercalação ao qual você se refere em sua listagem dos males dos arranjos de swap do FreeBSD 3.X?

O FreeBSD usa um intercalador de swap fixo, cujo padrão é 4. Isso significa que o FreeBSD reserva espaço para quatro áreas de swap, mesmo se você tiver apenas uma, duas ou três. Como a swap é intercalada, o espaço de endereçamento linear representando as quatro áreas de troca estará fragmentado se você não tiver quatro áreas de troca. Por exemplo, se você tiver duas áreas de swap, A e B, a representação do espaço de endereçamento do FreeBSD para esta área de troca será intercalada em blocos de 16 páginas:

A B C D A B C D A B C D A B C D

O FreeBSD 3.X usa uma abordagem de lista sequencial de regiões livres para contabilizar as áreas de swap livres. A ideia é que grandes blocos de espaço linear livre possam ser representados com um único nó da lista (kern/subr_rlist.c). Mas devido a fragmentação, a lista sequencial acaba sendo insanamente fragmentada. No exemplo acima, a swap completamente sem uso terá A e B mostrados como livres e C e D mostrados como todos alocados. Cada sequência A-B requer um nó da lista para considerar porque C e D são buracos, portanto, o nó de lista não pode ser combinado com a próxima sequência A-B.

Por que nós intercalamos nosso espaço de swap em vez de apenas colocar as áreas de swap no final e fazer algo mais sofisticado? Porque é muito mais fácil alocar trechos lineares de um espaço de endereçamento e ter o resultado automaticamente intercalado em vários discos do que tentar colocar esta sofisticação em outro lugar.

A fragmentação causa outros problemas. Sendo uma lista linear sob 3.X, e tendo uma enorme quantidade de fragmentação inerente, alocando e liberando swap leva a ser um algoritmo O(N) ao invés de um algoritmo O(1). Combinado com outros fatores (troca pesada) e você começa a entrar em níveis de sobrecarga O(N^2) e O(N^3), o que é ruim. O sistema 3.X também pode precisar alocar o KVM durante uma operação de troca para criar um novo nó da lista que pode levar a um impasse se o sistema estiver tentando fazer uma liberação de página em uma situação de pouca memória.

No 4.X, não usamos uma lista sequencial. Em vez disto, usamos uma árvore raiz e bitmaps de blocos de swap em vez de lista de nós variáveis. Aceitamos o sucesso de pré-alocar todos os bitmaps necessários para toda a área de swap na frente, mas acaba desperdiçando menos memória devido ao uso de um bitmap (um bit por bloco) em vez de uma lista encadeada de nós. O uso de uma árvore raiz em vez de uma lista sequencial nos dá quase o desempenho O(1), não importa o quão fragmentada a árvore se torne.

9.2.

Como a separação de páginas limpas e sujas (inativas) está relacionada à situação em que você vê baixas contagens de filas de cache e altas contagens de filas ativas no systat -vm? As estatísticas do systat rolam as páginas ativa e inativas juntas para a contagem de filas ativas?

Eu não entendo o seguinte:

É importante notar que o sistema de VM do FreeBSD tenta separar páginas limpas e inativas pelo motivo expresso de evitar descargas desnecessárias de páginas inativas (que consomem largura de banda de I/O), nem mover páginas entre as várias filas de páginas gratuitamente quando subsistema de memória não está sendo estressado. É por itso que você verá alguns sistemas com contagens de fila de cache muito baixas e contagens de fila ativa altas ao executar um comando systat -vm.

Sim, isto é confuso. A relação é meta versus realidade. Nosso objetivo é separar as páginas, mas a realidade é que, se não estamos em uma crise de memória, não precisamos realmente fazer isso.

O que isto significa é que o FreeBSD não tentará muito separar páginas sujas (fila inativa) de páginas limpas (fila de cache) quando o sistema não está sendo estressado, nem vai tentar desativar páginas (fila ativa -> fila inativa) quando o sistema não está sendo estressado, mesmo que não estejam sendo usados.

9.3.

No exemplo ls(1) / vmstat 1, algumas falhas de página não seriam falhas de página de dados (COW do arquivo executável para a página privada)? Ou seja, eu esperaria que as falhas de página fossem um preenchimento com zero e alguns dados do programa. Ou você está sugerindo que o FreeBSD faz pré-COW para os dados do programa?

Uma falha de COW pode ser preenchimento com zero ou dados de programa. O mecanismo é o mesmo dos dois modos, porque os dados do programa de apoio quase certamente já estão no cache. Eu estou realmente juntando os dois. O FreeBSD não faz o pré-COW dos dados do programa ou preenchimento com zero, mas faz pré-mapeamento de páginas que existem em seu cache.

9.4.

Em sua seção sobre otimizações de tabela de páginas, você pode dar um pouco mais de detalhes sobre pv_entry e vm_page (ou vm_page deveria ser vm_pmap- como em 4.4, cf. pp. 180-181 of McKusick, Bostic, Karel, Quarterman)? Especificamente, que tipo de operação/reação exigiria a varredura dos mapeamentos?

Como o Linux faz no caso onde o FreeBSD quebra (compartilhando um grande mapeamento de arquivos em muitos processos)?

Uma vm_page representa uma tupla (objeto,índice#). Um pv_entry representa uma entrada de tabela de página de hardware (pte). Se você tem cinco processos compartilhando a mesma página física, e três dessas tabelas de páginas atualmente mapeiam a página, esta página será representada por uma única estrutura vm_page e três estruturas pv_entry.

As estruturas pv_entry representam apenas as páginas mapeadas pela MMU (uma pv_entry representa uma pte). Isso significa que quando precisamos remover todas as referências de hardware para uma vm_page (para reutilizar a página para outra coisa, paginar, limpar, inativar e assim por diante), podemos simplesmente escanear a lista encadeada de pv_entry associada a essa vm_page para remover ou modificar os pte's de suas tabelas de páginas.

No Linux, não existe essa lista vinculada. Para remover todos os mapeamentos de tabelas de páginas de hardware para um vm_page, o linux deve indexar em todos os objetos de VM que possam ter mapeado a página. Por exemplo, se você tiver 50 processos, todos mapeando a mesma biblioteca compartilhada e quiser se livrar da página X nessa biblioteca, será necessário indexar na tabela de páginas para cada um desses 50 processos, mesmo se apenas 10 deles realmente tiverem mapeado a página. Então, o Linux está trocando a simplicidade de seu design com o desempenho. Muitos algoritmos de VM que são O(1) ou (pequeno N) no FreeBSD acabam sendo O(N), O(N^2), ou pior no Linux. Como os pte's que representam uma determinada página em um objeto tendem a estar no mesmo offset em todas as tabelas de páginas em que estão mapeados, reduzir o número de acessos nas tabelas de páginas no mesmo pte offset evitará a linha de cache L1 para esse deslocamento, o que pode levar a um melhor desempenho.

O FreeBSD adicionou complexidade (o esquema pv_entry) para aumentar o desempenho (para limitar os acessos da tabela de páginas a somente aqueles pte's que precisam ser modificados).

Mas o FreeBSD tem um problema de escalonamento que o Linux não possui, pois há um número limitado de estruturas pv_entry e isso causa problemas quando você tem um compartilhamento massivo de dados. Nesse caso, você pode ficar sem estruturas pv_entry, mesmo que haja bastante memória livre disponível. Isto pode ser corrigido com bastante facilidade aumentando o número de estruturas pv_entry na configuração do kernel, mas realmente precisamos encontrar uma maneira melhor de fazê-lo.

Em relação à sobrecarga de memória de uma tabela de páginas verso do esquema pv_entry: o Linux usa tabelas permanentes que não são descartadas, mas não precisa de um pv_entry para cada pte potencialmente mapeado. O FreeBSD usa tabelas de páginas throw away, mas adiciona em uma estrutura pv_entry para cada pte realmente mapeado. Eu acho que a utilização da memória acaba sendo a mesma, dando ao FreeBSD uma vantagem algorítmica com sua capacidade de jogar fora tabelas de páginas a vontade com uma sobrecarga muito baixa.

9.5.

Finalmente, na seção de page coloring, pode ser útil descrever um pouco mais o que você quer dizer aqui. Eu não segui bem isso.

Você sabe como funciona um cache de memória de hardware L1? Vou explicar: Considere uma máquina com 16MB de memória principal, mas apenas 128K de cache L1. Geralmente, a maneira como este cache funciona é que cada bloco de 128K de memória principal usa o mesmo 128K de cache. Se você acessar o offset 0 na memória principal e depois deslocar 128K na memória principal, você pode acabar jogando fora os dados em cache que você leu do offset 0!

Agora estou simplificando muito as coisas. O que acabei de descrever é o que é chamado de cache de memória de hardware diretamente mapeado. A maioria dos caches modernos são chamados de definição de associações de 2 vias ou definição de associações de 4 vias. A definição de associacões permite acessar até N regiões de memória diferentes que se sobrepõem à mesma memória de cache sem destruir os dados armazenados em cache anteriormente. Mas apenas N.

Então, se eu tenho um cache associativo de 4-way, eu posso acessar o offset 0, offset 128K, 256K e offset 384K e ainda ser capaz de acessar o offset 0 novamente e tê-lo vindo do cache L1. Se eu, então, acessar o deslocamento 512K, no entanto, um dos quatro objetos de dados armazenados anteriormente em cache será descartado pelo cache.

É extremamente importante… extremamente importante para que a maioria dos acessos de memória de um processador possam vir do cache L1, porque o cache L1 opera na frequência do processador. No momento em que você tem uma falha de cache L1 e precisa ir para o cache L2 ou para a memória principal, o processador irá parar e potencialmente sentar-se por centenas de instruções aguardando uma leitura de memória principal para completar. A memória principal (o ram dinâmico que você coloca em um computador) é lenta, quando comparada à velocidade de um núcleo de processador moderno.

Ok, agora em page coloring: Todos os caches de memória modernos são conhecidos como caches físicos. Eles armazenam em cache endereços de memória física, não endereços de memória virtual. Isto permite que o cache seja deixado sozinho em uma opção de contexto de processo, o que é muito importante.

Mas no mundo UNIX® você está lidando com espaços de endereço virtual, não com espaços de endereço físico. Qualquer programa que você escreva verá o espaço de endereço virtual dado a ele. As páginas reais físicas subjacentes a este espaço de endereço virtual não são necessariamente contíguas fisicamente! De fato, você pode ter duas páginas que estão lado a lado em um espaço de endereço de processos que termina no offset 0 e desloca 128K na memória física.

Um programa normalmente pressupõe que duas páginas lado a lado serão armazenadas em cache de maneira ideal. Ou seja, você pode acessar objetos de dados em ambas as páginas sem que elas descartem a entrada de cache uma da outra. Mas isso só é verdadeiro se as páginas físicas subjacentes ao espaço de endereço virtual forem contíguas (no que se refere ao cache).

É isso que o disfarce de página faz. Em vez de atribuir páginas físicas aleatórias a endereços virtuais, o que pode resultar em desempenho de cache não ideal, o disfarce de página atribui páginas físicas razoavelmente contíguas a endereços virtuais. Assim, os programas podem ser escritos sob a suposição de que as características do cache de hardware subjacente são as mesmas para seu espaço de endereço virtual, como seriam se o programa tivesse sido executado diretamente em um espaço de endereço físico.

Note que eu digo razoavelmente contíguo ao invés de simplesmente contíguo. Do ponto de vista de um cache mapeado direto de 128K, o endereço físico 0 é o mesmo que o endereço físico 128K. Assim, duas páginas lado a lado em seu espaço de endereço virtual podem acabar sendo compensadas em 128K e compensadas em 132K na memória física, mas também podem ser facilmente compensadas em 128K e compensadas em 4K na memória física e ainda manter as mesmas características de desempenho de cache. Portanto, disfarce de página não tem que atribuir páginas verdadeiramente contíguas de memória física a páginas contíguas de memória virtual, basta certificar-se de atribuir páginas contíguas do ponto de vista do desempenho e da operação do cache.

All FreeBSD documents are available for download at https://download.freebsd.org/ftp/doc/

Questions that are not answered by the documentation may be sent to <freebsd-questions@FreeBSD.org>.
Send questions about this document to <freebsd-doc@FreeBSD.org>.