ti-enxame.com

Gravações pequenas em SMB é lento no Windows, rápido na montagem CIFS Linux

Tenho lutado para corrigir um problema de desempenho com um compartilhamento SMB/CIFS ao executar pequenas gravações.

Primeiro, deixe-me descrever minha configuração de rede atual:

Servidor

  • Synology DS215j (com suporte SMB3 ativado)

Clientes (mesmo computador com inicialização dupla com fio Gig-E)

  • Ubuntu 14.04.5 LTS, Trusty Tahr
  • Windows 8.1

smb.conf

[global]
    printcap name=cups
    winbind enum groups=yes
    include=/var/tmp/nginx/smb.netbios.aliases.conf
    socket options=TCP_NODELAY IPTOS_LOWDELAY SO_RCVBUF=65536 SO_SNDBUF=65536
    security=user
    local master=no
    realm=*
    passdb backend=smbpasswd
    printing=cups
    max protocol=SMB3
    winbind enum users=yes
    load printers=yes
    workgroup=WORKGROUP

Atualmente, estou testando o pequeno desempenho de gravação com o seguinte programa escrito em C++ (no GitHub aqui ):

#include <iostream>
#include <fstream>
#include <sstream>

using namespace std;

int main(int argc, char* argv[])
{
    ofstream outFile(argv[1]);
    for(int i = 0; i < 1000000; i++)
    {
        outFile << "Line #" << i << endl;   
    }

    outFile.flush();
    outFile.close();
    return 0;
}

Configuração de montagem do Linux:

//192.168.1.10/nas-main on /mnt/nas-main type cifs (rw,noexec,nodev)

Programe o tempo de execução no Linux (atinge a saída da rede em ~ 100Mbps):

$ time ./nas-write-test /mnt/nas-main/home/will/test.txt

real    0m0.965s
user    0m0.148s
sys 0m0.672s

Instantâneo PCAP mostrando a divisão de várias linhas em um único pacote TCP:

Linux PCAP snapshot

Programe o tempo de execução no Windows, conforme medido pelo PowerShell:

> Measure-Command {start-process .\nas-write-test.exe -argumentlist "Z:\home\will\test-win.txt" -wait}


Days              : 0
Hours             : 0
Minutes           : 9
Seconds           : 29
Milliseconds      : 316
Ticks             : 5693166949
TotalDays         : 0.00658931359837963
TotalHours        : 0.158143526361111
TotalMinutes      : 9.48861158166667
TotalSeconds      : 569.3166949
TotalMilliseconds : 569316.6949

Instantâneo PCAP no Windows mostrando uma única linha por SMB Solicitação de gravação:

Windows PCAP snapshot

Este mesmo programa leva cerca de 10 minutos (~ 2.3Mbps) no Windows. Obviamente, o PCAP do Windows mostra uma conversa muito ruidosa SMB com eficiência de carga útil muito baixa.

Existem configurações no Windows que podem melhorar o desempenho de gravação pequena? Parece que, ao observar as capturas de pacotes, o Windows não armazena em buffer as gravações corretamente e envia imediatamente os dados, uma linha por vez. Considerando que, no Linux, os dados são fortemente armazenados em buffer e, portanto, têm desempenho muito superior. Deixe-me saber se os arquivos PCAP seriam úteis e posso encontrar uma maneira de enviá-los.

Atualização 10/27/16:

Como mencionado por @sehafoc, reduzi os servidores Samba max protocol configurando para SMB1 com o seguinte:

max protocol=NT1

A configuração acima resultou exatamente no mesmo comportamento.

Também removi a variável do Samba criando um compartilhamento em outra máquina Windows 10, e ela também exibe o mesmo comportamento do servidor Samba, por isso estou começando a acreditar que esse é um erro de cache de gravação nos clientes Windows em geral.

Atualização: 10/10/17:

Captura completa de pacotes do Linux (14MB)

Captura de pacotes completa do Windows (375MB)

Atualização: 10/10/17:

Também configurei um compartilhamento NFS e o Windows escreve sem buffer para isso também. Portanto, é definitivamente um problema subjacente do cliente Windows, tanto quanto posso dizer, o que é definitivamente lamentável: - /

Qualquer ajuda seria apreciada!

10
mevatron

O endl do C++ é definido para gerar '\ n' seguido de um flush. flush () é uma operação cara, portanto, você deve evitar o uso de endl como seu fim de linha padrão, pois isso pode criar exatamente o problema de desempenho que você está vendo (e não apenas com o SMB, mas com qualquer um dos fluxos com um flush caro, incluindo a rotação local Rust ou até o mais recente NVMe com uma taxa de saída ridiculamente alta).

Substituir endl por "\ n" corrigirá o desempenho acima, permitindo que o sistema faça buffer conforme o esperado. Exceto que algumas bibliotecas podem liberar "\ n"; nesse caso, você tem mais dores de cabeça (consulte https://stackoverflow.com/questions/21129162/tell-endl-not-to-flush para obter uma solução substituindo o método sync ()).

Agora, para complicar as coisas, flush () é definido apenas para o que acontece nos buffers da biblioteca. O efeito de liberação no sistema operacional, disco e outros buffers externos não está definido. Para Microsoft.NET "Quando você chama o método FileStream.Flush, o buffer de E/S do sistema operacional também é liberado." ( https://msdn.Microsoft.com/en-us/library/2bw4h516 (v = vs.110) .aspx ) Isso torna o flush particularmente caro para o Visual Studio C++, pois ele faz ida e volta escreva todo o caminho para a mídia física na extremidade do servidor remoto como você está vendo. O GCC, por outro lado, diz: "Um último lembrete: geralmente há mais buffers envolvidos do que apenas aqueles no nível da linguagem/biblioteca. Buffers de kernel, buffers de disco e similares também terão efeito. Inspecionar e alterar esses itens dependem do sistema . " ( https://gcc.gnu.org/onlinedocs/libstdc++/manual/streambufs.html ) Seus rastreios do Ubuntu parecem indicar que os buffers do sistema operacional/rede não são liberados pela liberação da biblioteca ( ) O comportamento dependente do sistema seria mais uma razão para evitar excessos e falhas. Se você estiver usando o VC++, tente mudar para um derivado do Windows GCC para ver como os comportamentos dependentes do sistema reagem ou, alternativamente, usar o Wine para executar o executável do Windows no Ubuntu.

Em geral, você precisa pensar nos seus requisitos para determinar se a descarga de cada linha é apropriada ou não. endl geralmente é adequado para fluxos interativos, como a exibição (precisamos que o usuário realmente veja nossa saída, e não em rajadas), mas geralmente não é adequado para outros tipos de fluxos, incluindo arquivos nos quais a sobrecarga de descarga pode ser significativa. Eu vi aplicativos liberados a cada gravação de 1, 2 e 4 e 8 bytes ... não é bonito ver o SO Grind milhões de E/S para gravar um arquivo de 1 MB.

Como exemplo, um arquivo de log pode precisar liberar todas as linhas se você estiver depurando uma falha porque precisa liberar o fluxo antes que a falha ocorra; enquanto outro arquivo de log pode não precisar liberar todas as linhas, se estiver produzindo apenas um log informativo detalhado que é esperado que seja liberado automaticamente antes que o aplicativo seja encerrado. Ele não precisa ser um ou outro, pois você pode derivar uma classe com um algoritmo de liberação mais sofisticado para atender a requisitos específicos.

Compare seu caso com o caso contrastante de pessoas que precisam garantir que seus dados sejam completamente persistentes no disco e não vulneráveis ​​em um buffer do sistema operacional ( https://stackoverflow.com/questions/7522479/how-do-i -ensure-data-is-write-to-disk-before-encerramento-fstream ).

Observe que, como está escrito, outFile.flush () é supérfluo, pois libera um fluxo já liberado. Para ser pedante, você deveria ter usado endl sozinho ou, de preferência "\ n", com outFile.flush (), mas não ambos.

2
Doug

O desempenho de operações remotas de arquivo, como leitura/gravação, usando o protocolo SMB pode ser afetado pelo tamanho dos buffers alocados pelos servidores e clientes. O tamanho do buffer determina o número de viagens de ida e volta necessárias para Sempre que os pedidos e respostas são enviados entre cliente e servidor, o tempo gasto é igual a pelo menos a latência entre os dois lados, o que pode ser muito significativo no caso da Wide Area Network (WAN). ).

Buffer SMB - O MaxBufferSize pode ser configurado através da seguinte configuração do Registro:

HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters\SizeReqBuf

Tipo de dados: REG_DWORD

Faixa: 1024 a 65535 (escolha o valor conforme sua exigência acima de 5000)

MAS SMB SIGNING afeta o tamanho máximo do buffer permitido. Portanto, precisamos desativar SMB a assinatura também para atingir nosso objetivo. O registro a seguir precisa ser criado nos dois servidor e, se possível, no cliente também.

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\LanManWorkstation\Parameters

Nome do valor: EnableSecuritySignature

Tipo de dados: REG_DWORD

Dados: 0 (desativar), 1 (ativar)

2
Adi Jha

Não tenho reputação suficiente para deixar um comentário (o que acho melhor, dado o nível de verificação desta resposta).

Percebo que uma grande variação no seu rastreamento de nível Linux x Windows é que você está usando o SMB1 no Linux e o SMB2 no Windows. Talvez o mecanismo de bloqueio de lote tenha um desempenho melhor no samba SMB1 do que na implementação de concessão exclusiva do SMB2. Nos dois casos, isso deve permitir uma certa quantidade de cache do lado do cliente.

1) Talvez tente definir um nível de protocolo máximo mais baixo no Samba para testar janelas com o SMB1 2) Valide se oplocks ou concessões exclusivos são retirados

Espero que isto ajude :)

2
sehafoc

Fenômeno interessante. Aqui está o que eu tentaria - não tenho idéia se isso realmente ajuda. Se fosse minha máquina, eu assistiria extensivamente aos SMB perfcounters. Um deles mostrará a causa.

Mais coisas para experimentar

Adicionar mais segmentos de trabalho

Caso o SMB_RDR atenda uma solicitação de E/S de gravação por linha (o que deve não acontecer aqui), isso pode ajudar a adicionar alguns threads ao mecanismo de execução.

Defina "AdditionalCriticalWorkerThreads" para 2 e depois para 4.

HKLM\System\CurrentControlSet\Control\Session Manager\Executive\AdditionalCriticalWorkerThreads

O padrão é 0, o que significa que nenhum encadeamento crítico adicional do operador do kernel foi adicionado. O que geralmente está ok. Este valor afeta o número de encadeamentos que o cache do sistema de arquivos usa para solicitações de leitura antecipada e gravação posterior. O aumento desse valor pode permitir mais E/S em fila no subsistema de armazenamento (o que é bom quando você deseja gravar linha por linha), mas é mais caro da CPU.

Adicionar mais comprimento da fila

Aumentar o valor "AdditionalCriticalWorkerThreads" aumenta o número de threads que o servidor de arquivos pode usar para atender solicitações simultâneas .

HKLM\System\CurrentControlSet\Services\LanmanServer\Parameters\MaxThreadsPerQueue

O padrão é 20. Uma indicação de que o valor pode precisar ser aumentado é se as filas de trabalho do SMB2 estiverem crescendo muito (o contador 'Filas de trabalho do servidor\Comprimento da fila\SMB2 *'. Deve ser <100).

1
bjoster