C# 在.NET中处理大型文件 问题

C# 在.NET中处理大型文件 问题,c#,.net,asynchronous,io,io-completion-ports,C#,.net,Asynchronous,Io,Io Completion Ports,我需要能够使用C#保存和读取非常大的数据结构。结构本身比较简单,;它是一个非常长的数组,由一个恒定大小的简单结构组成 为了清楚起见,举个例子: struct st { UInt32 a; UInt16 b; //etc. } completion ports st[] data = new st[1024*1024*100] 我希望能够保存和加载这些文件的速度和效率尽可能快 总方向 到目前为止,我的想法是将数据切割成段,当然,从概念上讲,将这些段分配给任务,然后将它们异步写入文件中。File

我需要能够使用C#保存和读取非常大的数据结构。结构本身比较简单,;它是一个非常长的数组,由一个恒定大小的简单结构组成

为了清楚起见,举个例子:

struct st {
UInt32 a;
UInt16 b;
//etc.
} completion ports

st[] data = new st[1024*1024*100]
我希望能够保存和加载这些文件的速度和效率尽可能快

总方向 到目前为止,我的想法是将数据切割成段,当然,从概念上讲,将这些段分配给任务,然后将它们异步写入文件中。FileStream.WriteAsync似乎非常适合于此

我的问题是读,从FielestRAM.Read SycAPI API看来,在每个结构中间都可以截取结果,实际上是在一个原语的中间,似乎是完全合理的。当然,我可以解决这个问题,但我不确定什么是最好的方法,以及我会在多大程度上干扰操作系统的缓冲机制

最后,我计划使用
MemoryStream.MemoryStream(byte[])
从每个缓冲区创建一个MemoryStream,并使用二进制读取器将每个缓冲区读入结构

问题 那么,解决这个问题的最佳方法是什么呢?我的方向好吗?有没有更好的解决办法? 代码示例和链接将不胜感激

结论 在进行性能测试后,我发现使用BinaryReader读取一个文件,或者使用FileStream.ReadAsync的多个读取器,可以获得大致相同的性能


苏。。。。这个问题毫无意义。

您最大的瓶颈将是IO,它必须以独占方式访问文件。实际的字节处理速度会很快——您也可以直接将其写入文件(注意,
FileStream
本身有一个缓冲区,或者您可以使用
BufferedStream
添加一个额外的层)而不是序列化内存中的不同部分,然后将每个内存中的部分分别复制到流中

我的建议是:只需在单个线程中写入数据。坦率地说,我甚至不确定我是否会为
async
(提示:async code会增加开销),尤其是在缓冲区保持不变的情况下。我也不会使用
BiaryWriter
/
BinaryReader
——我只会原封不动地编写它。您可以使用一些
不安全的
代码以块的形式复制数据,以避免查看单个对象,但这是最困难的事情。。。我将试着做一个例子

下面是一个读/写示例,首先注意性能:

Write: 2012ms
Read: 1089ms
File: 838,860,804 bytes
代码:


据我所知,读取或写入文件的最快方法是一个仅向前的进程。否则,除了强制读/写之外,磁盘还必须在文件中来回移动

当然,这并不意味着不能在多个并发线程中处理数据


如果段足够大,磁盘移动的开销可能不会引起注意。

[EDIT]我更新了这篇文章,以包含一个完整的可编译示例,并解决@Daniel在下面的评论中提出的问题。因此,此代码不再使用任何“危险”方法,并且没有代码分析警告。[/编辑]

如果您的结构只包含可blittable类型,那么有一种方法可以稍微加快速度

您可以使用封送处理将数据直接读取到数组中,而无需制作其他副本,例如(完整的可编译示例):

消费线程将从队列中删除内容,如下所示:

public void ProcessDataFromQueue<T>(BlockingCollection<T[]> queue) where T : struct
{
    foreach (var array in queue.GetConsumingEnumerable())
    {
        // Do something with 'array'
    }
}
public void ProcessDataFromQueue(BlockingCollection队列),其中T:struct
{
foreach(队列中的var数组。getConsuminegumerable())
{
//使用“数组”执行某些操作
}
}

问题解释得很好。也许从这里你可以得到一些想法:我认为用一条线写作也可以,我更关心阅读。。。我为什么要锁东西?FileStream.SomethingAsync不是使用完成端口吗?@AK_u文件只能位于一个位置;您需要知道您正在读取/写入文件的连续部分;添加了完整的读/写实现,btw@AK_哎呀,有一些虫子。。。现在修好了hopefully@MarcGravell事实上,我不需要知道我正在读取(或写入)文件的连续部分。我只需要知道我读过的缓冲区的位置,以及在我的数据中的相应位置…@AK_u再次强调,你错过了我的关键点:不管你怎么做,你的主要开销都是IO。在上面,我实际上只是在做IO和memcpy——后者速度非常快。由于没有工作要做,所以将其分片/分片到并发任务中并没有任何好处。通过添加并发,您所能实现的就是添加:查找时间;需要多个缓冲区;读/写时需要独占访问权限(是的,您需要此权限)。。。所有开销。仅仅使用不安全代码来直接填充或读取数据不是更快更简单吗?@AK_uu如果允许您使用不安全代码,那么很可能是这样,但这种方法可以避免不安全代码在项目中传播。一旦一个程序集使用它,您就必须在所有直接或间接使用它的项目上启用不安全代码——这对于某些站点(例如我们的站点)是不可接受的。这也可能不像你想象的那么容易做到@马修沃森同意最后一点;我花了3次才把我的示例(如下)弄对,我对使用原始内存时的序列化非常熟悉…@AK_u。这并不比意外指定超出边界的数组索引或尝试读取文件流结尾以外的内容更危险。使用我发布的代码不可能损坏内存或任何东西。封送员会抛出一个异常。@AK_u一如既往,这是一种折衷。你质疑州政府“尽可能快和高效”——我会回答说,
不安全的
/
memcpy
方法可能是(你会
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace ConsoleApplication1
{
    internal class Program
    {
        struct TestStruct // Mutable for brevity; real structs should be immutable.
        {
            public byte   ByteValue;
            public short  ShortValue;
            public int    IntValue;
            public long   LongValue;
            public float  FloatValue;
            public double DoubleValue;
        }

        static void Main()
        {
            var array = new TestStruct[10];

            for (byte i = 0; i < array.Length; ++i)
            {
                array[i].ByteValue   = i;
                array[i].ShortValue  = i;
                array[i].IntValue    = i;
                array[i].LongValue   = i;
                array[i].FloatValue  = i;
                array[i].DoubleValue = i;
            }

            Directory.CreateDirectory("C:\\TEST");

            using (var output = new FileStream(@"C:\TEST\TEST.BIN", FileMode.Create))
                FastWrite(output, array, 0, array.Length);

            using (var input = new FileStream(@"C:\TEST\TEST.BIN", FileMode.Open))
                array = FastRead<TestStruct>(input, array.Length);

            for (byte i = 0; i < array.Length; ++i)
            {
                Trace.Assert(array[i].ByteValue   == i);
                Trace.Assert(array[i].ShortValue  == i);
                Trace.Assert(array[i].IntValue    == i);
                Trace.Assert(array[i].LongValue   == i);
                Trace.Assert(array[i].FloatValue  == i);
                Trace.Assert(array[i].DoubleValue == i);
            }
        }

        /// <summary>
        /// Writes a part of an array to a file stream as quickly as possible,
        /// without making any additional copies of the data.
        /// </summary>
        /// <typeparam name="T">The type of the array elements.</typeparam>
        /// <param name="fs">The file stream to which to write.</param>
        /// <param name="array">The array containing the data to write.</param>
        /// <param name="offset">The offset of the start of the data in the array to write.</param>
        /// <param name="count">The number of array elements to write.</param>
        /// <exception cref="IOException">Thrown on error. See inner exception for <see cref="Win32Exception"/></exception>

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")]

        public static void FastWrite<T>(FileStream fs, T[] array, int offset, int count) where T: struct
        {
            int sizeOfT = Marshal.SizeOf(typeof(T));
            GCHandle gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);

            try
            {
                uint bytesWritten;
                uint bytesToWrite = (uint)(count * sizeOfT);

                if
                (
                    !WriteFile
                    (
                        fs.SafeFileHandle,
                        new IntPtr(gcHandle.AddrOfPinnedObject().ToInt64() + (offset*sizeOfT)),
                        bytesToWrite,
                        out bytesWritten,
                        IntPtr.Zero
                    )
                )
                {
                    throw new IOException("Unable to write file.", new Win32Exception(Marshal.GetLastWin32Error()));
                }

                Debug.Assert(bytesWritten == bytesToWrite);
            }

            finally
            {
                gcHandle.Free();
            }
        }

        /// <summary>
        /// Reads array data from a file stream as quickly as possible,
        /// without making any additional copies of the data.
        /// </summary>
        /// <typeparam name="T">The type of the array elements.</typeparam>
        /// <param name="fs">The file stream from which to read.</param>
        /// <param name="count">The number of elements to read.</param>
        /// <returns>
        /// The array of elements that was read. This may be less than the number that was
        /// requested if the end of the file was reached. It may even be empty.
        /// NOTE: There may still be data left in the file, even if not all the requested
        /// elements were returned - this happens if the number of bytes remaining in the
        /// file is less than the size of the array elements.
        /// </returns>
        /// <exception cref="IOException">Thrown on error. See inner exception for <see cref="Win32Exception"/></exception>

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")]

        public static T[] FastRead<T>(FileStream fs, int count) where T: struct
        {
            int sizeOfT = Marshal.SizeOf(typeof(T));

            long bytesRemaining  = fs.Length - fs.Position;
            long wantedBytes     = count * sizeOfT;
            long bytesAvailable  = Math.Min(bytesRemaining, wantedBytes);
            long availableValues = bytesAvailable / sizeOfT;
            long bytesToRead     = (availableValues * sizeOfT);

            if ((bytesRemaining < wantedBytes) && ((bytesRemaining - bytesToRead) > 0))
            {
                Debug.WriteLine("Requested data exceeds available data and partial data remains in the file.", "Dmr.Common.IO.Arrays.FastRead(fs,count)");
            }

            T[] result = new T[availableValues];

            if (availableValues == 0)
                return result;

            GCHandle gcHandle = GCHandle.Alloc(result, GCHandleType.Pinned);

            try
            {
                uint bytesRead;

                if
                (
                    !ReadFile
                    (
                        fs.SafeFileHandle,
                        gcHandle.AddrOfPinnedObject(),
                        (uint)bytesToRead,
                        out bytesRead,
                        IntPtr.Zero
                    )
                )
                {
                    throw new IOException("Unable to read file.", new Win32Exception(Marshal.GetLastWin32Error()));
                }

                Debug.Assert(bytesRead == bytesToRead);
            }

            finally
            {
                gcHandle.Free();
            }

            return result;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1415:DeclarePInvokesCorrectly")]
        [DllImport("kernel32.dll", SetLastError=true)]
        [return: MarshalAs(UnmanagedType.Bool)]

        private static extern bool WriteFile
        (
            SafeFileHandle       hFile,
            IntPtr               lpBuffer,
            uint                 nNumberOfBytesToWrite,
            out uint             lpNumberOfBytesWritten,
            IntPtr               lpOverlapped
        );

        /// <summary>See the Windows API documentation for details.</summary>

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1415:DeclarePInvokesCorrectly")]
        [DllImport("kernel32.dll", SetLastError=true)]
        [return: MarshalAs(UnmanagedType.Bool)]

        private static extern bool ReadFile
        (
            SafeFileHandle       hFile,
            IntPtr               lpBuffer,
            uint                 nNumberOfBytesToRead,
            out uint             lpNumberOfBytesRead,
            IntPtr               lpOverlapped
        );
    }
}
public void ReadIntoQueue<T>(FileStream fs, BlockingCollection<T[]> queue, int blockSize) where T: struct
{
    while (true)
    {
        var data = FastRead<T>(fs, blockSize);

        if (data.Length == 0)
        {
            queue.CompleteAdding();
            break;
        }

        queue.Add(data);
    }
}
public void ProcessDataFromQueue<T>(BlockingCollection<T[]> queue) where T : struct
{
    foreach (var array in queue.GetConsumingEnumerable())
    {
        // Do something with 'array'
    }
}