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# File.ReadAllBytes vs std::ifstream (Windows)

相同的结果

深入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由;

  1. 为网络上读取的每个文件创建一个相同的文件。文件大小~ 37 mb。
  2. 使用c#主循环调用SLT (std::ifstream),然后以交替的方式调用Windows ReadFile方法,用于50个文件(每个文件25个)。
  3. 重复步骤2对于不同的读取大小{KB*1, KB*4, KB*32, MB*1, MB*5}
  4. 等待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++实现中使用相同的缓冲区大小