Pequenas gravações no compartilhamento de rede SMB são lentas no Windows, rápido em relação à montagem CIFS do Linux

11

Eu 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 a 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 (picos de saída de rede a ~ 100 Mbps):

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

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

Instantâneo do PCAP mostrando o agrupamento de muitas linhas em um único pacote TCP:

ProgrameotempodeexecuçãonoWindowsconformemedidopeloPowerShell:

>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 do PCAP no Windows mostrando uma única linha por solicitação de gravação do SMB:

Estemesmoprogramalevacercade10minutos(~2,3Mbps)noWindows.Obviamente,oPCAPdoWindowsmostraumaconversamuitobarulhentacompoucaeficiênciadecargaútil.

ExistemconfiguraçõesnoWindowsquepodemmelhorarodesempenhodegravaçãopequena?Pareceque,olhandoparaascapturasdepacotes,oWindowsnãoarmazenaembufferasgravaçõescorretamenteeimediatamenteenviaosdadosumalinhaporvez.Considerandoque,noLinux,osdadossãostrongmentearmazenadosembuffere,portanto,têmumdesempenhomuitosuperior.Deixe-mesaberseosarquivosPCAPseriamúteisepossoencontrarumamaneiradeenviá-los.

Atualização27/10/16:

Comomencionadopor@sehafoc,reduziaconfiguraçãodemaxprotocoldosservidoresSambaparaSMB1comoseguinte:

maxprotocol=NT1

Aconfiguraçãoacimaresultounomesmocomportamentoexato.

EutambémremoviavariáveldoSambacriandoumcompartilhamentoemoutramáquinaWindows10,eelatambémexibeomesmocomportamentoqueoservidorSamba,entãoestoucomeçandoaacreditarqueissoéumerrodecachedegravaçãocomclientesWindowsemgeral.

Atualização:10/06/17:

Captura completa de pacotes Linux (14MB)

Captura completa de pacotes do Windows (375 MB)

Atualização: 10/12/17:

Eu também configuro um compartilhamento NFS e o Windows escreve sem buffer para isso também. Então, é definitivamente um problema subjacente do cliente do Windows, tanto quanto eu posso dizer, o que é definitivamente infeliz: - /

Qualquer ajuda seria apreciada!

    
por mevatron 12.10.2016 / 17:23

4 respostas

1

O endl C ++ é definido para saída '\ n' seguido por um flush. flush () é uma operação cara, então você deve geralmente evitar usar endl como seu final de linha padrão, pois ele pode criar exatamente o problema de desempenho que você está vendo (e não apenas com SMB, mas com um ofstream com flush caro incluindo spinning local). ferrugem ou até mesmo o último NVMe em alguma taxa ridiculamente alta de produção).

Substituir endl por "\ n" corrigirá o desempenho acima, permitindo que o sistema faça o buffer como pretendido. Exceto que algumas bibliotecas podem liberar "\ n", caso em que você tem mais dores de cabeça (consulte link para 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 o Microsoft.NET "Quando você chama o método FileStream.Flush, o buffer de E / S do sistema operacional também é liberado." ( link ) Isso torna o flush particularmente caro para o Visual Studio C ++ como ele fará a viagem de ida e volta até a mídia física na extremidade do servidor remoto, como você está vendo. O GCC, por outro lado, diz "Um último lembrete: há geralmente mais buffers envolvidos do que apenas aqueles no nível de linguagem / biblioteca. Buffers de kernel, buffers de disco e similares também terão efeito. Inspecionar e alterar esses são dependentes do sistema " ( link ) Seus rastreamentos do Ubuntu parecem indicar que os buffers do sistema operacional / rede não são liberados pela biblioteca flush (). Comportamento dependente do sistema seria mais uma razão para evitar endl e flushing excessivamente. Se você estiver usando o VC ++, pode tentar alternar para um derivativo 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.

Mais geralmente, você precisa pensar sobre seus requisitos para determinar se a liberação de cada linha é apropriada ou não. O endl é geralmente adequado para fluxos interativos, como o display (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 em que a sobrecarga de liberação pode ser significativa. Já vi apps flush em todas as gravações de 1 e 2 e 4 e 8 bytes ... não é bonito ver o SO triturar milhões de IOs para gravar um arquivo de 1 MB.

Como exemplo, um arquivo de log pode precisar liberar cada linha se você estiver depurando uma falha, porque você precisa liberar o fluxo antes de ocorrer a falha; enquanto outro arquivo de log pode não precisar liberar todas as linhas se estiver produzindo um registro informativo detalhado que deve ser executado automaticamente antes que o aplicativo seja encerrado. Não precisa ser ou / ou como você pode derivar uma classe com um algoritmo flush 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 mantidos em disco e não vulneráveis em um buffer do sistema operacional ( link ).

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

    
por 13.10.2017 / 09:15
3

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

Noto que uma grande variação no rastreamento do nível do Linux vs 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 a implementação exclusiva de concessão de SMB2. Em ambos os casos, isso deve permitir uma certa quantidade de cache no lado do cliente.

1) Talvez tente definir um nível mais baixo de protocolo máximo no Samba para testar janelas com o SMB1 2) Confirme que os bloqueios ou arrendamentos exclusivos são retirados

Espero que isso ajude:)

    
por 13.10.2016 / 23:12
3

O desempenho das operações de arquivos remotos, 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 enviar uma quantidade fixa de dados. Toda vez que solicitações e respostas são enviadas 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 de WAN (Wide Area Network, rede de longa distância).

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

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

Tipo de dados: REG_DWORD

Intervalo: 1024 a 65535 (Escolha o valor conforme sua necessidade acima de 5000)

MAS A ASSINATURA DE SMB afeta o tamanho máximo do buffer permitido. Assim, precisamos desabilitar a assinatura SMB, bem como atingir nossa meta. O registro seguinte precisa ser criado no lado do servidor e, se possível, no lado do cliente também.

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\LanManWorkstation\Parameters

Nome do valor: EnableSecuritySignature

Tipo de dados: REG_DWORD

Dados: 0 (desabilitar), 1 (habilitar)

    
por 13.10.2016 / 05:28
1

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

Mais coisas para tentar

Adicione mais segmentos de trabalho

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

Defina "AdditionalCriticalWorkerThreads" para 2 e, em seguida, para 4.

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

O padrão é 0, o que significa que nenhum encadeamento crítico de trabalho do kernel foi adicionado. Qual é geralmente ok. Esse valor afeta o número de encadeamentos que o cache do sistema de arquivos usa para solicitações de leitura antecipada e write-behind. Aumentar esse valor can permite mais E / S na fila no subsistema de armazenamento (o que é bom quando você deseja escrever linha por linha), mas é mais caro para a CPU.

Adicionar mais comprimento da fila

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

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

O padrão é 20. Uma indicação de que o valor precisa ser aumentado é se as filas de trabalho do SMB2 estiverem crescendo muito (perfcounter 'Filas de Trabalho do Servidor \ Comprimento da Fila \ SMB2 *'. deve ser < 100).

    
por 09.10.2017 / 17:27