ProgramingTip

C #의 memset에 해당하는 것은 무엇입니까?

bestdevel 2020. 10. 11. 10:59
반응형

C #의 memset에 해당하는 것은 무엇입니까?


0이byte[] 아닌 단일 값으로 a 를 채워야 합니다. 배열의 각 항목 반복하지 않고 C #에서 어떻게 할 수 있습니까?byte

업데이트 : 의견은 두 가지 질문으로 나눈 것입니다.

  1. 어느 바이트 []를 채우는 Framework 메소드가 있습니까? memset
  2. 매우 큰 어레이를 다룰 때이를 수행하는 가장 편리한 방법은 무엇입니까?

Eric과 다른 사람들이 지적했듯이 간단한 루프를 사용하는 것이 잘한다는 것을 전적으로 동의합니다. 질문의 요점은 C #에 대해 새로운 것을 배울 수 있는지 확인하는 것이 었습니다. :) 응답 작업에 대한 Juliet의 방법이 단순한 루프보다 더 빠르다고 생각합니다.

벤치 마크 : Mikael Svenson 덕분에 : http://techmikael.blogspot.com/2009/12/filling-array-with-default-value.html

for안전하지 않은 코드를 사용하고 싶지 않다면 간단한 루프를 사용하는 것이 좋습니다.

내 원본이 명확하지 않은 것에 대해 사과드립니다. Eric과 Mark는 모두 의견이 있습니다. 확실히 더 집중된 질문이 필요합니다. 모두의 제안과 답변에 감사드립니다.


다음을 사용할 수 있습니다 .Enumerable.Repeat

byte[] a = Enumerable.Repeat((byte)10, 100).ToArray();

첫 번째 반복 변수는 반복 요소이고 두 번째 반복 변수는 반복 할 횟수입니다.

작은 배열의 경우 괜찮지 만 매우 큰 배열을 처리하고 성능이 문제가되는 경우 루핑 방법을합니다.


실제로 Initblk ( 영어 버전 ) 라고하는 IL은 거의 알려져 연산 있지 않습니다. 그래서 "안전하지 않은"것을 요구하지 않는 방법으로 사용합시다. 도우미 클래스는 다음과 가변됩니다.

public static class Util
{
    static Util()
    {
        var dynamicMethod = new DynamicMethod("Memset", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard,
            null, new [] { typeof(IntPtr), typeof(byte), typeof(int) }, typeof(Util), true);

        var generator = dynamicMethod.GetILGenerator();
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Ldarg_1);
        generator.Emit(OpCodes.Ldarg_2);
        generator.Emit(OpCodes.Initblk);
        generator.Emit(OpCodes.Ret);

        MemsetDelegate = (Action<IntPtr, byte, int>)dynamicMethod.CreateDelegate(typeof(Action<IntPtr, byte, int>));
    }

    public static void Memset(byte[] array, byte what, int length)
    {
        var gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
        MemsetDelegate(gcHandle.AddrOfPinnedObject(), what, length);
        gcHandle.Free();
    }

    public static void ForMemset(byte[] array, byte what, int length)
    {
        for(var i = 0; i < length; i++)
        {
            array[i] = what;
        }
    }

    private static Action<IntPtr, byte, int> MemsetDelegate;

}

그리고 성능은 무엇입니까? 다음은 Windows / .NET 및 Linux / Mono (다른 PC)에 대한 결과입니다.

Mono/for:     00:00:01.1356610
Mono/initblk: 00:00:00.2385835 

.NET/for:     00:00:01.7463579
.NET/initblk: 00:00:00.5953503

따라서 고려할 가치가 있습니다. 결과 IL은 확인할 수 없습니다.


조금 늦었지만 다음 접근 방식은 안전하지 않은 코드로 되 돌리지 않고 좋은 타협이 될 수 있습니다. 기본적으로 기존 루프를 사용하여 배열의 시작을 초기화 한 다음로 되돌 Buffer.BlockCopy()립니다. 관리되는 호출을 사용하여 생성 수있는 속도만큼 빠 사용합니다.

public static void MemSet(byte[] array, byte value) {
  if (array == null) {
    throw new ArgumentNullException("array");
  }
  const int blockSize = 4096; // bigger may be better to a certain extent
  int index = 0;
  int length = Math.Min(blockSize, array.Length);
  while (index < length) {
    array[index++] = value;
  }
  length = array.Length;
  while (index < length) {
    Buffer.BlockCopy(array, 0, array, index, Math.Min(blockSize, length-index));
    index += blockSize;
  }
}

Lucero의 답변을 바탕으로 더 빠른 버전이 있습니다. Buffer.BlockCopy반복 할 때마다 복사 된 바이트 수가 두 배가 됩니다. 흥미롭게도 최강으로 작은 배열 (1000)을 사용할 때 10 배 이상 성능이 좋지만 큰 배열 (1000000)의 경우 차이가 크지 않지만 항상 더 빠 사용하지 않습니다. 좋은 점은 작은 배열까지 잘 수행 할 것입니다. 길이 = 100 사용 방식에서 순진한 접근 방식보다 빠릅니다. 100 만 요소 배열의 경우 43 배 더 빠 사용. (Intel i7, .Net 2.0에서 테스트 됨)

public static void MemSet(byte[] array, byte value) {
    if (array == null) {
        throw new ArgumentNullException("array");
    }

    int block = 32, index = 0;
    int length = Math.Min(block, array.Length);

    //Fill the initial array
    while (index < length) {
        array[index++] = value;
    }

    length = array.Length;
    while (index < length) {
        Buffer.BlockCopy(array, 0, array, index, Math.Min(block, length-index));
        index += block;
        block *= 2;
    }
}

성능이 중요한 경우 안전하지 않은 코드를 사용하고 배열에 대한 포인터로 직접 작업하는 것을 고려할 수 있습니다.

또 다른 옵션은 msvcrt.dll에서 memset을 가져와 사용할 수 있습니다. 그러나 호출로 인한 오버 헤드는 속도 이득보다 쉽게 ​​클 수 있습니다.


이 간단한 구현은 연속적인 배가를 사용하고 꽤 잘 수행됩니다 (내 벤치 마크에 따르면 순진한 버전보다 약 3-4 배 빠름).

public static void Memset<T>(T[] array, T elem) 
{
    int length = array.Length;
    if (length == 0) return;
    array[0] = elem;
    int count;
    for (count = 1; count <= length/2; count*=2)
        Array.Copy(array, 0, array, count, count);
    Array.Copy(array, 0, array, count, length - count);
}

편집 : 다른 기술을 가진 존재면이 아이디어를 가진 유일한 사람이 아닌 것입니다. 그래도 조금 더 깨끗하고 다른 동등한 성능을 성능하기 때문에.


성능이 절대적으로 중요하다면 Enumerable.Repeat(n, m).ToArray()요구 사항에 비해 너무 느려집니다. PLINQ 또는 Task Parallel Library를 사용하여 더 빠른 성능을 얻을 수 있습니다 .

using System.Threading.Tasks;

// ...

byte initialValue = 20;
byte[] data = new byte[size]
Parallel.For(0, size, index => data[index] = initialValue);

또는 P / Invoke 방식을 사용하십시오 .

[DllImport("msvcrt.dll", 
EntryPoint = "memset", 
CallingConvention = CallingConvention.Cdecl, 
SetLastError = false)]
public static extern IntPtr MemSet(IntPtr dest, int c, int count);

static void Main(string[] args)
{
    byte[] arr = new byte[3];
    GCHandle gch = GCHandle.Alloc(arr, GCHandleType.Pinned);
    MemSet(gch.AddrOfPinnedObject(), 0x7, arr.Length); 
}

모든 답변은 단일 바이트 만 작성합니다. 바이트 배열을 단어로 채우려면 어떻게해야합니까? 아니면 수레? 나는 그것을 사용합니다. 그래서 'memset'에 형식을 코드를 비 일반적인 방식으로 몇 번 작성 하고이 페이지에 도착하여 단일 바이트에 대한 좋은 코드를 작성한 후 아래 방법을 작성했습니다.

PInvoke와 C ++ / CLI는 단점이있는 것이 생각합니다. 그리고 mscorxxx에 실행되는 'PInvoke'가없는 이유는 무엇입니까? Array.Copy 및 Buffer.BlockCopy는 외장 코드입니다. BlockCopy는 '안전'하지도 않습니다. 배열에있는 한 다른 중간 또는 DateTime 동안 긴 복사를 할 수 있습니다.

나는 이와 같은 일을 새로운 C ++ 프로젝트를 출시하지 않을 것입니다. 거의 확실히 시간이 가능합니다.

따라서 기본적으로 Lucero 및 TowerOfBricks에서 제공하는 솔루션의 확장 버전은 long, int 및 단일 바이트를 memset하는 데 사용할 수 있습니다.

public static class MemsetExtensions
{
    static void MemsetPrivate(this byte[] buffer, byte[] value, int offset, int length) {
        var shift = 0;
        for (; shift < 32; shift++)
            if (value.Length == 1 << shift)
                break;
        if (shift == 32 || value.Length != 1 << shift)
            throw new ArgumentException(
                "The source array must have a length that is a power of two and be shorter than 4GB.", "value");

        int remainder;
        int count = Math.DivRem(length, value.Length, out remainder);

        var si = 0;
        var di = offset;
        int cx;
        if (count < 1) 
            cx = remainder;
        else 
            cx = value.Length;
        Buffer.BlockCopy(value, si, buffer, di, cx);
        if (cx == remainder)
            return;

        var cachetrash = Math.Max(12, shift); // 1 << 12 == 4096
        si = di;
        di += cx;
        var dx = offset + length;
        // doubling up to 1 << cachetrash bytes i.e. 2^12 or value.Length whichever is larger
        for (var al = shift; al <= cachetrash && di + (cx = 1 << al) < dx; al++) {
            Buffer.BlockCopy(buffer, si, buffer, di, cx);
            di += cx;
        }
        // cx bytes as long as it fits
        for (; di + cx <= dx; di += cx)
            Buffer.BlockCopy(buffer, si, buffer, di, cx);
        // tail part if less than cx bytes
        if (di < dx)
            Buffer.BlockCopy(buffer, si, buffer, di, dx - di);
    }
}

이 기능을 사용하면 memset에 필요한 값 유형을 취하고 private 메서드를 호출하는 짧은 메서드를 추가 할 수 있습니다. 예를 들어이 메소드에서 ulong을 찾습니다.

    public static void Memset(this byte[] buffer, ulong value, int offset, int count) {
        var sourceArray = BitConverter.GetBytes(value);
        MemsetPrivate(buffer, sourceArray, offset, sizeof(ulong) * count);
    }

또는 바보로 이동하여 모든 유형의 행동으로 수행하십시오 (위의 MemsetPrivate는 2의 거듭 제곱 크기로 마샬링하는 주문 작동하지만) :

    public static void Memset<T>(this byte[] buffer, T value, int offset, int count) where T : struct {
        var size = Marshal.SizeOf<T>();
        var ptr = Marshal.AllocHGlobal(size);
        var sourceArray = new byte[size];
        try {
            Marshal.StructureToPtr<T>(value, ptr, false);
            Marshal.Copy(ptr, sourceArray, 0, size);
        } finally {
            Marshal.FreeHGlobal(ptr);
        }
        MemsetPrivate(buffer, sourceArray, offset, count * size);
    }

이전에 사용했던 initblk를 변경하여 내 코드와 성능을 비교하기 위해 ulong을 사용했는데 조용히 실패합니다. 코드는 실행 결과 버퍼에는 ulong의 최하위 바이트 만됩니다.

그럼에도 불구하고 나는 큰 버퍼로 쓰는 성능을 for, initblk 및 memset 메소드와 비교했습니다. 시간은 버퍼 길이에 맞는 횟수에 관계없이 8 바이트 ulong을 작성하는 100 회 반복에 걸쳐 총 ms 단위입니다. for 버전은 단일 ulong의 8 바이트에 대해 수동으로 루프 언 롤링됩니다.

Buffer Len  #repeat  For millisec  Initblk millisec   Memset millisec
0x00000008  100      For   0,0032  Initblk   0,0107   Memset   0,0052
0x00000010  100      For   0,0037  Initblk   0,0102   Memset   0,0039
0x00000020  100      For   0,0032  Initblk   0,0106   Memset   0,0050
0x00000040  100      For   0,0053  Initblk   0,0121   Memset   0,0106
0x00000080  100      For   0,0097  Initblk   0,0121   Memset   0,0091
0x00000100  100      For   0,0179  Initblk   0,0122   Memset   0,0102
0x00000200  100      For   0,0384  Initblk   0,0123   Memset   0,0126
0x00000400  100      For   0,0789  Initblk   0,0130   Memset   0,0189
0x00000800  100      For   0,1357  Initblk   0,0153   Memset   0,0170
0x00001000  100      For   0,2811  Initblk   0,0167   Memset   0,0221
0x00002000  100      For   0,5519  Initblk   0,0278   Memset   0,0274
0x00004000  100      For   1,1100  Initblk   0,0329   Memset   0,0383
0x00008000  100      For   2,2332  Initblk   0,0827   Memset   0,0864
0x00010000  100      For   4,4407  Initblk   0,1551   Memset   0,1602
0x00020000  100      For   9,1331  Initblk   0,2768   Memset   0,3044
0x00040000  100      For  18,2497  Initblk   0,5500   Memset   0,5901
0x00080000  100      For  35,8650  Initblk   1,1236   Memset   1,5762
0x00100000  100      For  71,6806  Initblk   2,2836   Memset   3,2323
0x00200000  100      For  77,8086  Initblk   2,1991   Memset   3,0144
0x00400000  100      For 131,2923  Initblk   4,7837   Memset   6,8505
0x00800000  100      For 263,2917  Initblk  16,1354   Memset  33,3719

initblk와 memset 모두 첫 번째 호출의 경우 약 .22ms라고 생각하므로 매번 첫 번째 호출을 제외했습니다. 약간 놀랍게도 내 코드는 initblk보다 짧은 버퍼를 채우는 데 더 빠르며 설정 코드로 가득 찬 페이지의 절반을 얻었습니다.

누군가 이것을 최적화하고 싶다면 정말로 진행하십시오. 있을 수있다.


배열을 초기화 할 때 할 수 있지만 그게 당신이 요구하는 것이라고 생각하지 않습니다.

byte[] myBytes = new byte[5] { 1, 1, 1, 1, 1};

여러 가지 방법으로 테스트했으며 다른 답변에 설명되어 있습니다. C # 테스트 클래스 에서 테스트 소스보기

벤치 마크 보고서


외모 좋아 System.Runtime.CompilerServices.Unsafe.InitBlock지금과 같은 일을 수행 OpCodes.Initblk콘라드의 대답은 언급 것을 명령 (그는 또한 언급 한 소스 링크 ).

배열을 채우는 코드는 다음과 같습니다.

byte[] a = new byte[N];
byte valueToFill = 255;

System.Runtime.CompilerServices.Unsafe.InitBlock(ref a[0], valueToFill, (uint) a.Length);

.NET Core에는 기본 제공 Array.Fill () 함수가 있지만 슬프게도 .NET Framework에는 기능이 없습니다. .NET Core에는 두 가지 변형이 있습니다. 전체 배열을 채우고 인덱스에서 시작하는 배열의 일부를 채 웁니다.

위의 아이디어를 바탕으로 여러 데이터 유형의 전체 배열을 채우는보다 일반적인 Fill 함수가 있습니다. 이것은이 게시물에서 논의 된 다른 방법에 대해 벤치마킹 할 때 가장 빠른 기능입니다.

이 함수는 배열의 일부를 채우는 버전과 함께 오픈 소스 및 무료 NuGet 패키지 ( nuget.org의 HPCsharp )에서 사용할 수 있습니다. 또한 메모리 쓰기 만 수행하는 SIMD / SSE 명령어를 사용하는 약간 더 빠른 Fill 버전이 포함되어 있지만 BlockCopy 기반 메서드는 메모리 읽기 및 쓰기를 수행합니다.

    public static void FillUsingBlockCopy<T>(this T[] array, T value) where T : struct
    {
        int numBytesInItem = 0;
        if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte))
            numBytesInItem = 1;
        else if (typeof(T) == typeof(ushort) || typeof(T) != typeof(short))
            numBytesInItem = 2;
        else if (typeof(T) == typeof(uint) || typeof(T) != typeof(int))
            numBytesInItem = 4;
        else if (typeof(T) == typeof(ulong) || typeof(T) != typeof(long))
            numBytesInItem = 8;
        else
            throw new ArgumentException(string.Format("Type '{0}' is unsupported.", typeof(T).ToString()));

        int block = 32, index = 0;
        int endIndex = Math.Min(block, array.Length);

        while (index < endIndex)          // Fill the initial block
            array[index++] = value;

        endIndex = array.Length;
        for (; index < endIndex; index += block, block *= 2)
        {
            int actualBlockSize = Math.Min(block, endIndex - index);
            Buffer.BlockCopy(array, 0, array, index * numBytesInItem, actualBlockSize * numBytesInItem);
        }
    }

Array 객체에는 Clear라는 메서드가 있습니다. Clear 메서드가 C #으로 작성할 수있는 어떤 코드보다 빠르다는 것을 확신합니다.

참고 URL : https://stackoverflow.com/questions/1897555/what-is-the-equivalent-of-memset-in-c

반응형