C# File.ReadAllBytes vs std::ifstream (Windows)
本文关键字:ifstream Windows std File ReadAllBytes vs | 更新日期: 2023-09-27 18:12:33
在c#和c++中,从网络文件中读取所有字节会得到非常不同的结果。c++花的时间几乎是c#
的三倍c#File.ReadAllBytes(filepath);
c++ std::ifstream str_in(fp, std::ifstream::binary);
int data_rem = fs_len;
int data_read = 0;
while (data_rem>0)
{
str_in.read(buffer+ data_read, 1024*1024*1);
data_rem -= str_in.gcount();
data_read += str_in.gcount();
}
使用C结构体FILE得到与c++
深入c#源代码,我们可以看到它正在从kernal32.dll中调用ReadFile,在c++中复制相同的代码可以获得相同的性能。
HANDLE f_handle = CreateFile(fp, GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD read=0;
ReadFile(f_handle, buffer, fs_len, &read, NULL);
CloseHandle(f_handle);
https://msdn.microsoft.com/en-us/library/windows/desktop/aa365430 (v = vs.85) . aspx
我进行了一些进一步的测试,并给出了完整的代码/结果如下。
Test由;
- 为网络上读取的每个文件创建一个相同的文件。文件大小~ 37 mb。
- 使用c#主循环调用SLT (std::ifstream),然后以交替的方式调用Windows ReadFile方法,用于50个文件(每个文件25个)。
- 重复步骤2对于不同的读取大小{KB*1, KB*4, KB*32, MB*1, MB*5}
- 等待20分钟,确保缓存不会影响任何东西,然后重做步骤2和3
所有代码在发布模式下编译,visual studio 2015社区版。
c#调用代码
[DllImport(DLL_NAME, CharSet = CharSet.Unicode)]
public static extern int test(string data_in, int option, int block_size);
...
string fp_template = @"N:/foo/{0}/bar ({1}).csv";
int KB = 1024;
int MB = KB * 1024;
int block_id = 0;
foreach (var block_size in new int[] {KB*1,KB*4,KB*32,MB*1,MB*5 })
{
Console.WriteLine("BlockSize (KB): " +(block_size/1024));
for (int i = 0; i < 50; i++)
{
Stopwatch sw_loop = Stopwatch.StartNew();
ParseBlockNative.test(String.Format(fp_template,block_id, i), i % 2,block_size);
Console.WriteLine("Option: {0} Taken: {1}", i % 2 == 0 ? "0-SLT" : "1-WIN", sw_loop.ElapsedMilliseconds);
}
Console.WriteLine();
block_id++;
}
c++ IO Code
extern "C" int DllExport test(wchar_t* fp,int option,int block_size)
{
int fs_len = 38441760;
char* buffer = (char*)malloc(fs_len);
int data_rem = fs_len;
int data_read = 0;
if (option==0)
{
std::ifstream str_in(fp, std::ifstream::binary);
while (data_rem > 0)
{
str_in.read(buffer + data_read, min(block_size,data_rem));
data_rem -= str_in.gcount();
data_read += str_in.gcount();
}
}
else
{
HANDLE f_handle = CreateFile(fp, GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
while (data_rem > 0)
{
DWORD read = 0;
ReadFile(f_handle, buffer + data_read, min(block_size, data_rem), &read, NULL);
data_rem -= read;
data_read += read;
}
CloseHandle(f_handle);
}
free(buffer);
return data_read;
}
这些测试结果已被取代,请跳到下一节
从网络读取37MB文件的时间,平均每个数据点超过25个文件。
取1
+------------+------+----------+
| Block_Size | SLT | Kernel32 |
+------------+------+----------+
| 1KB | 957 | 1065 |
| 4KB | 965 | 953 |
| 32KB | 952 | 729 |
| 1MB | 1015 | 230 |
| 5MB | 993 | 231 |
+------------+------+----------+
第2次(20分钟后重新进行相同的测试)
+------------+------+----------+
| Block_Size | SLT | Kernel32 |
+------------+------+----------+
| 1KB | 1040 | 999 |
| 4KB | 1077 | 1102 |
| 32KB | 1079 | 784 |
| 1MB | 1028 | 231 |
| 5MB | 1035 | 201 |
+------------+------+----------+
的评论:更改读取大小不会改善STL结果。Kernel32倍从4KB到32KB略有提高,从32KB到1MB有很大提高。
我现在的预感是,作为一个网络驱动器,延迟非常高,但带宽很大。大量的小请求(~4KB)要比少量的大请求(~1MB)慢得多。延迟比我们在普通SSD或HDD上看到的要大得多,因此对于更多的请求来说,代价更大。尽管我告诉STL读取1MB的数据块,但结果表明它忽略了这一点,并请求了一些4KB的读取。
更新 -唯一数据
在上面的章节中看到的Kernel32从32KB到1MB的速度提升对我来说有点难以置信。我为每个读取操作使用一个唯一的文件,但内容是相同的。我担心存在基于数据内容的某种程度的缓存。我重新做了上面的测试,但这次我为每个读操作随机生成唯一的文件。
下面的每个数据点代表25个随机生成内容的不同文件的平均值。
+------------+---------+----------+
| Block_Size | SLT | Kernel32 |
+------------+---------+----------+
| 1KB | 976 | 1,101 |
| 4KB | 1,027 | 1,011 |
| 32KB | 969 | 768 |
| 1MB | 981 | 530 |
| 5MB | 1,008 | 541 |
+------------+---------+----------+
为完整起见,表示c#文件所花费的时间。ReadAllBytes的完成时间大约是585ms,接近kernel32mb的时间。
我重新做了上面测试的一个子集,将FILE_FLAG_NO_BUFFERING传递给Kernel32, 1MB运行时增加到大约700ms,但仍然明显优于STL。
的评论:在我看来,服务器端网络缓存的影响已经被消除了。我看到的是c# 'Kernel32执行了SLT。在SLT中,通过增加读取大小没有看到性能的提高。
如果你得到不同的时间,这可能是因为。net默认为文件IO使用4kB缓冲区,比你的c++代码使用的1kB缓冲区大4倍。
编辑:请记住,由于这是一个网络文件,因此每次读取缓冲区都会增加网络延迟。因此,缓冲区越小,读取越多,增加的延迟和其他开销也就越多。
编辑:我没有看到整行代码,并且错过了缓冲区大小上额外的*1024块。所以你的缓冲区实际上是1mb。为了公平地比较两者,请在c#和c++实现中使用相同的缓冲区大小