当前位置: 首页 > news >正文

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的实现,需要支持的功能有:构造函数、析构函数、取值赋值

构造函数

    //构造函数中主要逻辑时做内存分配,和数据Copypublic 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);//创建一个原子操作安全的HandleInitStaticSafetyId(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++的memcpygCHandle.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);//释放AtomicSafetyHandleUnsafeUtility.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;}


http://www.mrgr.cn/news/60761.html

相关文章:

  • 【设计模式系列】适配器模式(九)
  • 自然语言处理:第五十七章 RoPE旋转位置编码
  • 如何排查断连问题——《OceanBase诊断系列》十三
  • 海外云手机怎样助力亚马逊店铺运营?
  • 【c语言测试】
  • 基于SSM+微信小程序的汽车预约维修管理系统(汽车3)
  • 100种算法【Python版】第21篇——Wilson算法
  • Java Lock CountDownLatch 总结
  • 李宇皓现身第十届“文荣奖”,allblack造型帅气绅士引关注
  • 加强版 第一节图像二值化定义
  • 四、常量指针其他
  • 信创认证(信创人才考评证书)的含金量?到底有多少?
  • 【Flask】三、Flask 常见项目架构
  • IPV6扩展头部
  • SQL进阶技巧:Hive如何进行更新和删除操作?
  • 自修室预约系统|基于java和小程序的自修室预约系统设计与实现(源码+数据库+文档)
  • 代码随想录第46天|
  • 前端:遇到的面试题
  • Oracle 第10章:触发器
  • Spring MVC介绍
  • Spring Boot 3项目创建与示例(Web+JPA)
  • 江协科技STM32学习- P23 DMA 直接存储器存取
  • CSS.选择器
  • Java性能调优与垃圾回收机制(4/5)
  • 当代AI大模型产品经理现状,及产品经理转型方向?
  • QT 机器视觉 (3. 虚拟相机SDK、测试工具)