8

私は次のC構造を持っています

struct MyStruct {
    char chArray[96];
    __int64 offset;
    unsigned count;
}

私は今、何千もの構造を持つ C で作成されたたくさんのファイルを持っています。C# を使用してそれらを読み取る必要があり、速度が問題です。

私はC#で次のことをしました

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 108)]
public struct PreIndexStruct {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 96)]
    public string Key;
    public long Offset;
    public int Count;
}

そして、次を使用してファイルからデータを読み取ります

using (BinaryReader br = new BinaryReader(
       new FileStream(pathToFile, FileMode.Open, FileAccess.Read, 
                      FileShare.Read, bufferSize))) 
{
    long length = br.BaseStream.Length;
    long position = 0;

    byte[] buff = new byte[structSize];
    GCHandle buffHandle = GCHandle.Alloc(buff, GCHandleType.Pinned);
    while (position < length) {
        br.Read(buff, 0, structSize);
        PreIndexStruct pis = (PreIndexStruct)Marshal.PtrToStructure(
            buffHandle.AddrOfPinnedObject(), typeof(PreIndexStruct));
        structures.Add(pis);

        position += structSize;
    }
    buffHandle.Free();
}

これは完全に機能し、ファイルからデータを正常に取得できます。

GCHandle.Alloc/Marshal.PtrToStructure を使用する代わりに、C++/CLI または C# の安全でないコードを使用すると、高速化できることを読みました。いくつかの例を見つけましたが、それらは固定サイズの配列のない構造のみを参照しています。

私の質問は、私の特定のケースについて、C++/CLI または C# の安全でないコードを使用して物事を行うためのより高速な方法はありますか?

編集

追加のパフォーマンス情報 (ANTS Performance Profiler 7.4 を使用しました):

CPU 時間の 66% が Marshal.PtrToStructure の呼び出しに使用されています。

I/O に関しては、ファイルからの読み取りに使用されるのは 105 ミリ秒のうち 6 ミリ秒だけです。

4

3 に答える 3

4

この場合、マネージ コードとネイティブ コードの間で構造体をやり取りする必要がないため、P/Invoke を明示的に使用する必要はありません。したがって、代わりにこれを行うことができます。この無駄な GC ハンドルの割り当てを回避し、必要なものだけを割り当てます。

public struct PreIndexStruct {
    public string Key;
    public long Offset;
    public int Count;
}

while (...) {
    ...
    PreIndexStruct pis = new PreIndexStruct();
    pis.Key = Encoding.Default.GetString(reader.ReadBytes(96));
    pis.Offset = reader.ReadInt64();
    pis.Count = reader.ReadInt32();
    structures.Add(pis);
}

あなたがこれよりずっと速くなれるかどうかはわかりません。

于 2013-01-30T14:36:23.480 に答える
1

おそらくより正確には、アンマネージ コードを使用したい場合は、次のようにします。

  1. C++/CLI プロジェクトを作成し、既存の C# コードをそこに移植して実行する
  2. ボトルネックがどこにあるかを特定します (プロファイラーを使用)
  3. コードのそのセクションをストレートな C++ で書き直し、C++/CLI コードから呼び出して、動作することを確認し、再度プロファイリングします。
  4. 新しいコードを「#pragma unmanaged」で囲みます
  5. もう一度プロファイリングする

おそらくある程度の速度向上が得られるでしょうが、期待したものではないかもしれません。

于 2013-01-30T14:31:49.000 に答える
0

構造体のいくつかの配列の非常に迅速な読み取りを行うことは非常に手間がかかりますが、この手法には blittable 型が必要であるため、それを行う唯一の方法は、文字列を使用する代わりにキーのバイトの固定バッファーを作成することです。

それを行うと、安全でないコードを使用する必要があるため、おそらくそれほど価値はありません。

ただし、好奇心旺盛な人のために、安全でないコードと多くのフィドルを許可する必要があるという犠牲を払って、これらの構造体の超高速の読み取りと書き込みを行う方法を次に示します。

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;


namespace Demo
{
    public static class Program
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 108)]
        public struct PreIndexStruct
        {
            public unsafe fixed byte Key[96];
            public long Offset;
            public int Count;
        }

        private static void Main(string[] args)
        {
            PreIndexStruct[] a = new PreIndexStruct[100];

            for (int i = 0; i < a.Length; ++i)
            {
                a[i].Count = i;

                unsafe
                {
                    fixed (byte* key = a[i].Key)
                    {
                        for (int j = 0; j < 10; ++j)
                        {
                            key[j] = (byte)i;
                        }
                    }
                }
            }

            using (var output = File.Create(@"C:\TEST\TEST.BIN"))
            {
                FastWrite(output, a, 0, a.Length);
            }

            using (var input = File.OpenRead(@"C:\TEST\TEST.BIN"))
            {
                var b = FastRead<PreIndexStruct>(input, a.Length);

                for (int i = 0; i < b.Length; ++i)
                {
                    Console.Write("Count = " + b[i].Count + ", Key =");

                    unsafe
                    {
                        fixed (byte* key = b[i].Key)
                        {
                            // Here you would access the bytes in Key[], which would presumably be ANSI chars.

                            for (int j = 0; j < 10; ++j)
                            {
                                Console.Write(" " + key[j]);
                            }
                        }
                    }

                    Console.WriteLine();
                }
            }
        }

        /// <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>

        [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId="System.Runtime.InteropServices.SafeHandle.DangerousGetHandle")]

        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.DangerousGetHandle(),
                        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>

        [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId="System.Runtime.InteropServices.SafeHandle.DangerousGetHandle")]

        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];

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

            try
            {
                uint bytesRead = 0;

                if
                (
                    !ReadFile
                    (
                        fs.SafeFileHandle.DangerousGetHandle(),
                        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;
        }


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

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

        private static extern bool ReadFile
        (
            IntPtr hFile,
            IntPtr lpBuffer,
            uint nNumberOfBytesToRead,
            out uint lpNumberOfBytesRead,
            IntPtr lpOverlapped
        );

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

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

        private static extern bool WriteFile
        (
            IntPtr hFile,
            IntPtr lpBuffer,
            uint nNumberOfBytesToWrite,
            out uint lpNumberOfBytesWritten,
            IntPtr lpOverlapped
        );
    }
}
于 2013-01-30T14:42:56.687 に答える