Unity Job System详解(3)——NativeArray源码分析

【特性】

Unity特性:

  • [NativeContainer]表明其是一个NativeContainer
  • [ReadOnly]表示只读
  • [WriteAccessRequired]表示要有写操作
  • [NativeDisableUnsafePtrRestriction] 允许使用Unsafe代码,当数据量大时,copy可能费时,需要用指针,写unsafe代码会用到
  • [NativeDisableParallelForRestrictionAttribute] 允许多线程写入,在并行Job中常用,自己维护好,不同索引的Index。
  •  [NativeDisableContainerSafetyRestriction]禁用job的 safety system,让你对NativeArray拥有完全的控制权,同时系统也就不会帮你定位race condition等情况,所以在使用的时候,需要自己确保安全性
  • [NativeContainerSupportsMinMaxWriteRestriction]用于限制NativeContainer中的元素在一定范围内进行写操作。通过设置最小值和最大值,可以确保在修改NativeContainer中的元素时不会超出指定范围,从而避免出现意外的错误或问题。

C#特性:

  • [MethodImpl(MethodImplOptions.AggressiveInlining)]指定编译器对该函数进行内联
  • [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]编译器指令,条件为真时才会编译输出

【源码】

定义

public struct NativeArray<T> : IDisposable, IEnumerable<T>, IEnumerable, IEquatable<NativeArray<T>> where T : struct

继承三个接口(一般自定义底层数据结构都会继承这三个),约束泛型是结构体

可以参考C# Array的实现,需要支持的功能有:构造函数、析构函数、取值赋值

构造函数

    //构造函数中主要逻辑时做内存分配,和数据Copy
    public NativeArray(T[] array, Allocator allocator)
    {
        if (array == null)
        {
            throw new ArgumentNullException("array");
        }

        Allocate(array.Length, allocator, out this);
        Copy(array, this);
    }

    //数据在Buffer指针
    //[NativeDisableUnsafePtrRestriction]
    //internal unsafe void* m_Buffer;

    //内存分配:Unity自己会管理内存,根据allocator做不同类型的内存分配,每块内存与SafetyId一一对应
    private unsafe static void Allocate(int length, Allocator allocator, out NativeArray<T> array)
    {
        long size = (long)UnsafeUtility.SizeOf<T>() * (long)length; //计算结构体占用内存大小,实际调用System.Runtime.CompilerServices.Unsafe.SizeOf,其获取对象占用内存大小时可以绕过C#语言的类型安全检查
        CheckAllocateArguments(length, allocator);
        array = default(NativeArray<T>);
        IsUnmanagedAndThrow();
        array.m_Buffer = UnsafeUtility.MallocTracked(size, UnsafeUtility.AlignOf<T>(), allocator, 0);//注意分配内存时,传的时size,而不是long,写C++应该很明白,C#写多了,容易糊涂
        array.m_Length = length; //length就和C# Array一样
        array.m_AllocatorLabel = allocator;
        array.m_MinIndex = 0;
        array.m_MaxIndex = length - 1;
        AtomicSafetyHandle.CreateHandle(out array.m_Safety, allocator);//创建一个原子操作安全的Handle
        InitStaticSafetyId(ref array.m_Safety);//用TypeNameBytes和bytesCount算了一个SafetyId,一般也就那几种Hash算法来算
        InitNestedNativeContainer(array.m_Safety);
    }


    private unsafe static void CopySafe(T[] src, int srcIndex, NativeArray<T> dst, int dstIndex, int length)
    {
        AtomicSafetyHandle.CheckWriteAndThrow(dst.m_Safety);
        CheckCopyPtr(src);//关闭ENABLE_UNITY_COLLECTIONS_CHECKS,这些Check都能去掉,对性能提升有益
        CheckCopyArguments(src.Length, srcIndex, dst.Length, dstIndex, length);
        GCHandle gCHandle = GCHandle.Alloc(src, GCHandleType.Pinned);
        IntPtr intPtr = gCHandle.AddrOfPinnedObject();//用GCHandle拿到托管对象的指针
        UnsafeUtility.MemCpy((byte*)dst.m_Buffer + dstIndex * UnsafeUtility.SizeOf<T>(), (byte*)(void*)intPtr + srcIndex * UnsafeUtility.SizeOf<T>(), length * UnsafeUtility.SizeOf<T>());
        //拷贝,需要传入目的地址指针、源地址指针,拷贝数据大小,底层实际调用的是C++的memcpy
        gCHandle.Free();//释放GCHandle
    }

其他重载的构造函数、Allocate和CopySafe大同小异

取值赋值

    // 取值赋值
    public unsafe T this[int index]
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get
        {
            CheckElementReadAccess(index);
            return UnsafeUtility.ReadArrayElement<T>(m_Buffer, index);
            //调用System.Runtime.CompilerServices.Unsafe.Read<T>((byte*)source + (long)index * (long)System.Runtime.CompilerServices.Unsafe.SizeOf<T>());
            //这里的意思是找到要读的数据的开始的地址,为buffer的地址加上索引乘以T的大小,读取的长度为T的大小。T只是数据的标识方式
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        [WriteAccessRequired]
        set
        {
            CheckElementWriteAccess(index);
            UnsafeUtility.WriteArrayElement(m_Buffer, index, value);
            调用 System.Runtime.CompilerServices.Unsafe.Write((byte*)destination + (long)index * (long)System.Runtime.CompilerServices.Unsafe.SizeOf<T>(), value);
        }
    }

析构函数

    [WriteAccessRequired]
    public unsafe void Dispose()
    {
        if (m_AllocatorLabel != Allocator.None && !AtomicSafetyHandle.IsDefaultValue(in m_Safety))
        {
            AtomicSafetyHandle.CheckExistsAndThrow(in m_Safety);
        }

        if (IsCreated)
        {
            if (m_AllocatorLabel == Allocator.Invalid)
            {
                throw new InvalidOperationException("The NativeArray can not be Disposed because it was not allocated with a valid allocator.");
            }

            if (m_AllocatorLabel > Allocator.None)
            {
                AtomicSafetyHandle.DisposeHandle(ref m_Safety);//释放AtomicSafetyHandle
                UnsafeUtility.FreeTracked(m_Buffer, m_AllocatorLabel);//释放内存,由于是Native内存,必须显式调用释放接口
                m_AllocatorLabel = Allocator.Invalid;
            }

            m_Buffer = null;//指针置空
        }
    }

IEquatable接口实现

自定义数据结构实现IEquatable时一般要实现Equals方法,并重写判等操作符

    // buffer地址和长度相等,才相等
    public unsafe bool Equals(NativeArray<T> other)
    {
        return m_Buffer == other.m_Buffer && m_Length == other.m_Length;
    }

    // 重载操作符
    public static bool operator ==(NativeArray<T> left, NativeArray<T> right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(NativeArray<T> left, NativeArray<T> right)
    {
        return !left.Equals(right);
    }

迭代器太常见了不说了

NativeArray.Dispose(JobHandle)

当某个Job依赖NativeArray时,该NativeArray需要在该Job完成后释放,可以使用该接口。

其为该NativeArray创建一个依赖传入的JobHandle的NativeArrayDisposeJob

   if (m_AllocatorLabel > Allocator.None)
      {
          NativeArrayDisposeJob jobData = default(NativeArrayDisposeJob);
          jobData.Data = new NativeArrayDispose
          {
              m_Buffer = m_Buffer,
              m_AllocatorLabel = m_AllocatorLabel,
              m_Safety = m_Safety
          };
          JobHandle result = jobData.Schedule(inputDeps);
          AtomicSafetyHandle.Release(m_Safety);
          m_Buffer = null;
          m_AllocatorLabel = Allocator.Invalid;
          return result;
      }

上一篇:【移动应用开发】Android持久化技术


下一篇:redis集群(主从同步、哨兵、群集)