byte array allocation causing large slowdown in time-critical method

0

I'm working on a high-performance app that makes a pInvoke dll call to call winapi DeviceIoControl. DeviceIoControl requires a byte buffer.

        public byte[] Read(IntPtr address, int length) {
            var info = new MemOperation();
            var buf = new byte[length];

            info.Pid = Pid;

            if (address == H.pBaseCtxAddress) {
                address = (IntPtr) ((ulong) address + (ulong) KeReadModuleBase());
            }

            info.Addr = address;
            info.WriteBuffer = IntPtr.Zero;
            info.Size = buf.Length;


            var bytes = 0;
            if (address != IntPtr.Zero && (long) address > 0 && (long) address != 0 &&
                (long) address < 0x7FFFFFFFFFFF) {
                var res = Win32.DeviceIoControl(Handle, CtlCode(0x00000022, IOCTL_READ_MEM, 2, 0), info,
                    Marshal.SizeOf(info), buf, (uint) length, ref bytes, IntPtr.Zero);
            }

            return buf;
        }

When running Visual Studio 2019's profiler, it lists the byte array's declaration as a hotspot.

            var buf = new byte[length];

I am not constrained to safe or managed code, so I am open for unsafe solutions for this. I have debugged the actual pInvoke call, and it's as performant as it can be. The profiler doesn't list it as a hotspot either, only the byte array allocation.

I assume this is happening due to how C# is creating the new byte array. It's my understanding C# zero's out every byte when allocating the array. Is there a way instead that I can simply select a block of free memory of n length? The Driver that this IOCTL is calling is always going to return the specified length, so having dirty memory (non-zero'd) shouldn't be an issue.

This is the DllImport I have for DeviceIoControl:

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        [SuppressUnmanagedCodeSecurity]
        public static extern bool DeviceIoControl(
            IntPtr hDevice,
            uint IoControlCode,
            [MarshalAs(UnmanagedType.AsAny)][In] object InBuffer,
            int nInBufferSize,
            [MarshalAs(UnmanagedType.AsAny)][Out] object OutBuffer,
            uint nOutBufferSize,
            ref int pBytesReturned,
            IntPtr Overlapped
        );

Here's the full call-path for this (so you understand how I'm using the results):

Multiple threads are calling Read<T> which is in turn calling the function Read that contains the byte array allocation that's posted at the beginning of the thread. The profiler lists this allocation as a hotspot. The final GetStructure converts that byte array to a struct.

        public T Read<T>(IntPtr address) where T: unmanaged {
            var size = Marshal.SizeOf(typeof(T));
            var data = Read(address, size);
            return GetStructure<T>(data);
        }

        public unsafe static T GetStructure<T>(byte[] bytes) where T: unmanaged {
            fixed (byte* p = bytes)
            {
                T structure = *(T*)p;
                return structure;
            }
        }
c#
arrays
memory
asked on Stack Overflow May 7, 2021 by Ben • edited May 7, 2021 by Ben

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0