在 Windows 上,对 SMB 网络共享的小写入速度很慢,而在 CIFS Linux 挂载上则很快

在 Windows 上,对 SMB 网络共享的小写入速度很慢,而在 CIFS Linux 挂载上则很快

我一直在努力修复执行小型写入时 SMB/CIFS 共享的性能问题。

首先,让我描述一下我当前的网络设置:

服务器

  • Synology DS215j(启用 SMB3 支持)

客户端(同一台计算机双启动有线 Gig-E)

  • Ubuntu 14.04.5 LTS,值得信赖的塔尔羊
  • Windows 8.1

smb配置文件

[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

我目前正在使用以下用 C++ 编写的程序测试小写入性能(在 GitHub 上这里):

#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;
}

Linux 挂载配置:

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

Linux 上的程序运行时间(网络输出峰值约为 100Mbps):

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

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

PCAP 快照显示多行分块为单个 TCP 数据包:

Linux PCAP 快照

使用 PowerShell 测量的 Windows 程序运行时间:

> 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

Windows 上的 PCAP 快照显示每个 SMB 写入请求单行:

Windows PCAP 快照

同一程序在 Windows 上大约需要 10 分钟(~2.3Mbps)。显然,Windows PCAP 显示的 SMB 对话非常嘈杂,有效载荷效率非常低。

Windows 上是否有任何设置可以提高小写入性能?从数据包捕获来看,Windows 似乎没有正确缓冲写入,而是立即一次一行地发送数据。而在 Linux 上,数据被大量缓冲,因此性能优越得多。如果 PCAP 文件有用,请告诉我,我可以找到上传它们的方法。

2016年10月27日更新:

正如@sehafoc 所述,我max protocol使用以下命令将 Samba 服务器设置降低为 SMB1:

max protocol=NT1

上述设置导致完全相同的行为。

我还通过在另一台 Windows 10 机器上创建共享删除了 Samba 的变量,它也表现出与 Samba 服务器相同的行为,所以我开始相信这是一个普遍存在的 Windows 客户端写入缓存错误。

更新:2017 年 10 月 6 日:

完整的 Linux 数据包捕获 (14MB)

完整的 Windows 数据包捕获 (375MB)

更新:2017年10月12日:

我还设置了 NFS 共享,Windows 也确实在无缓冲的情况下写入。所以,据我所知,这肯定是一个潜在的 Windows 客户端问题,这绝对令人遗憾 :-/

任何帮助,将不胜感激!

答案1

C++ endl 被定义为输出 '\n' 后跟一个 flush。flush() 是一个昂贵的操作,因此您通常应避免使用 endl 作为默认行尾,因为它可能会造成您所看到的性能问题(不仅仅是 SMB,还有任何具有昂贵刷新的 ofstream,包括本地旋转 rust 或甚至最新的 NVMe,其输出率极高)。

将 endl 替换为“\n”将通过允许系统按预期缓冲来解决上述性能问题。但有些库可能会在“\n”上刷新,在这种情况下你会遇到更多麻烦(请参阅https://stackoverflow.com/questions/21129162/tell-endl-not-to-flush寻找覆盖 sync() 方法的解决方案)。

现在,事情变得更加复杂,flush() 仅针对库缓冲区内发生的事情进行定义。flush 对操作系统、磁盘和其他外部缓冲区的影响尚未定义。对于 Microsoft.NET,“当您调用 FileStream.Flush 方法时,操作系统 I/O 缓冲区也会被刷新。”(https://msdn.microsoft.com/en-us/library/2bw4h516(v=vs.110).aspx) 这使得刷新对于 Visual Studio C++ 来说特别昂贵,因为它会将写入一直往返到远程服务器远端的物理介质,正如您所看到的。另一方面,GCC 说“最后提醒:通常涉及的缓冲区不仅仅是语言/库级别的缓冲区。内核缓冲区、磁盘缓冲区等也会产生影响。检查和更改这些内容取决于系统。” (https://gcc.gnu.org/onlinedocs/libstdc++/manual/streambufs.html) 您的 Ubuntu 跟踪似乎表明操作系统/网络缓冲区未被库 flush() 刷新。系统相关行为更应避免使用 endl 和过度刷新。如果您使用的是 VC++,您可以尝试切换到 Windows GCC 衍生产品,看看系统相关行为如何反应,或者使用 Wine 在 Ubuntu 上运行 Windows 可执行文件。

更一般地,您需要考虑您的要求以确定是否适合刷新每一行。endl 通常适用于交互式流,例如显示(我们需要用户实际看到我们的输出,而不是突发的),但通常不适合其他类型的流,包括刷新开销可能很大的文件。我见过应用程序在每次写入 1、2、4 和 8 字节时刷新一次……看到操作系统耗费数百万次 IO 来写入 1MB 文件,这可不是什么好事。

例如,如果您正在调试崩溃,则日志文件可能需要刷新每一行,因为您需要在崩溃发生之前刷新 ofstream;而另一个日志文件可能不需要刷新每一行,如果它只是生成详细的信息日志,预计会在应用程序终止前自动刷新。它不需要非此即彼,因为您可以派生一个具有更复杂刷新算法的类来满足特定要求。

将您的情况与需要确保其数据完全保存到磁盘且不会在操作系统缓冲区中受到攻击的人的情况进行比较(https://stackoverflow.com/questions/7522479/how-do-i-ensure-data-is-written-to-disk-before-closing-fstream)。

请注意,正如所写,outFile.flush() 是多余的,因为它会刷新已经刷新的 ofstream。为了严谨起见,您应该单独使用 endl 或最好将“\n”与 outFile.flush() 一起使用,但不要同时使用两者。

答案2

我没有足够的声誉来发表评论(考虑到这个答案的验证级别,我认为发表评论会更好)。

我注意到,Linux 与 Windows 级别跟踪的一个巨大差异是您在 Linux 上使用 SMB1,而在 Windows 上使用 SMB2。也许批处理 oplock 机制在 SMB1 Samba 中的表现优于 SMB2 独占租约实现。在这两种情况下,这些都应该允许一定数量的客户端缓存。

1) 或许可以尝试在 Samba 中设置较低的最大协议级别,以使用 SMB1 尝试使用窗口 2) 验证是否已取出独占 oplock 或租约

希望这可以帮助 :)

答案3

使用 SMB 协议执行远程文件操作(例如读/写)的性能可能会受到服务器和客户端分配的缓冲区大小的影响。缓冲区大小决定了发送固定量数据所需的往返次数。每次在客户端和服务器之间发送请求和响应时,所花费的时间至少等于双方之间的延迟,这在广域网 (WAN) 的情况下可能非常显著。

SMB 缓冲区——可以通过以下注册表设置配置 MaxBufferSize:

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

数据类型: REG_DWORD

范围:1024 至 65535(根据您的要求选择 5000 以上的值)

但是 SMB 签名会影响允许的最大缓冲区大小。因此,我们还需要禁用 SMB 签名才能实现我们的目标。需要在服务器端创建以下注册表,如果可能的话,还需要在客户端创建以下注册表。

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\LanManWorkstation\Parameters

值名称:EnableSecuritySignature

数据类型:REG_DWORD

数据:0(禁用),1(启用)

答案4

有趣的现象。以下是我想尝试的方法 - 我不知道这是否真的有帮助。如果是我的计算机,我会广泛观察 SMB 性能计数器。其中之一将要說明原因。

更多尝试

添加更多工作线程

如果 SMB_RDR 每行出现一个写入 I/O 请求(应该不是发生在这里),它可能帮助向执行引擎添加一些线程。

将“AdditionalCriticalWorkerThreads”设置为 2,然后设置为 4。

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

默认值为 0,这意味着不添加额外的关键内核工作线程。这通常是可以的。此值会影响文件系统缓存用于预读和后写请求的线程数。提高此值允许在存储子系统中进行更多排队 I/O(当您想要逐行写入时,这很好),但它会消耗更多的 CPU 资源。

添加更多队列长度

增加“AdditionalCriticalWorkerThreads”值会增加文件服务器可用于服务的线程数同时要求。

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

默认值为 20。如果 SMB2 工作队列变得非常大(性能计数器“Server Work Queues\Queue Length\SMB2*”应小于 100),则可能需要增加该值。

相关内容