在 Windows 上,對 SMB 網路共享的少量寫入速度很慢,在 CIFS Linux 掛載上速度很快
在執行小型寫入時,我一直在努力解決 SMB/CIFS 共享的性能問題。
首先,讓我描述一下我目前的網路設置:
伺服器
- Synology DS215j(啟用 SMB3 支持)
客戶端(同一台電腦雙啟動有線 Gig-E)
- Ubuntu 14.04.5 LTS,可信賴的 Tahr
- 視窗 8.1
配置文件
[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 數據包:
由 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 上,同樣的程序大約需要 10 分鐘 (~2.3Mbps)。顯然,Windows PCAP 顯示了非常嘈雜的 SMB 對話,負載效率非常低。
Windows 上是否有任何設置可以提高小型寫入性能?從數據包擷取看來,Windows 沒有正確緩衝寫入並立即一次一行地發送數據。而在 Linux 上,數據被大量緩衝,因此具有非常出色的性能。讓我知道 PCAP 文件是否有幫助,我可以找到上傳它們的方法。
2016 年 10 月 27 日更新:
正如@sehafoc 所提到的,我將 Samba 伺服器
max protocol
設置減少到 SMB1,如下所示:
max protocol=NT1
上述設置導致完全相同的行為。
我還通過在另一台 Windows 10 機器上創建共享來刪除了 Samba 的變數,它也表現出與 Samba 伺服器相同的行為,所以我開始相信這通常是 Windows 客戶端的寫入記憶體錯誤。
更新:2017 年 10 月 6 日:
更新:2017 年 10 月 12 日:
我還設置了一個 NFS 共享,Windows 也沒有為此進行緩衝寫入。所以,據我所知,這絕對是一個潛在的 Windows 客戶端問題,這絕對是不幸的:-/
任何幫助,將不勝感激!
C++ endl 被定義為輸出 ‘\n’ 後跟一個刷新。flush() 是一項昂貴的操作,因此您通常應避免使用 endl 作為您的預設行尾,因為它可以準確地創建您所看到的性能問題(不僅僅是 SMB,而是任何具有昂貴刷新的 ofstream,包括本地旋轉rust 甚至是最新的 NVMe,輸出率高得離譜)。
將 endl 替換為 “\n” 將通過允許系統按預期緩衝來修復上述性能。除了某些庫可能會在“\n”上刷新,在這種情況下您會更頭疼(請參閱https://stackoverflow.com/questions/21129162/tell-endl-not-to-flush以獲取覆蓋 sync() 方法的解決方案)。
現在更複雜的是,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-closure-fstream)。
請注意,正如所寫,outFile.flush() 是多餘的,因為它會刷新已刷新的 ofstream。為了迂腐,您應該單獨使用 endl 或最好將 “\n” 與 outFile.flush() 一起使用,但不能同時使用兩者。