Unity JobSystem 原理初探(1) - Job.Execute()

Unity JobSystem 原理初探(1) - Job.Execute()

  • 本文分析了JobSystem中IJobParallelFor的多线程实现
  • 代码来源:C#部分 C++部分来自于IDA pro解析UnityPlayer.dll

Unity中的C#脚本如下:

public class MyJobBehavior : MonoBehaviour
{
    void Update()
    {
        var input = new NativeArray<float>(10000, Allocator.Persistent);
        var output = new NativeArray<float>(10000, Allocator.Persistent);
        for (int i = 0; i < input.Length; i++)
            input[i] = 1.0f * i;

        var job = new MyJob
        {
            Input = input,
            Output = output
        };
        job.Schedule(10000,64).Complete();

        input.Dispose();
        output.Dispose();
    }
    
    private struct MyJob : IJobParallelFor
    {
        [ReadOnly]
        public NativeArray<float> Input;

        [WriteOnly]
        public NativeArray<float> Output;

        public void Execute(int i)
        {
            Output[i] = Input[i]*100;
        }
    }
}

我先来研究job.Schedule,再C#接口调用如下,可以看到JobsUtility提供了进入Unity内核入口.

        unsafe public static JobHandle Schedule<T>(this T jobData, int arrayLength, int innerloopBatchCount, JobHandle dependsOn = new JobHandle()) where T : struct, IJobParallelFor
        {
            var scheduleParams = new JobsUtility.JobScheduleParameters(UnsafeUtility.AddressOf(ref jobData), ParallelForJobStruct<T>.jobReflectionData, dependsOn, ScheduleMode.Parallel);
            return JobsUtility.ScheduleParallelFor(ref scheduleParams, arrayLength, innerloopBatchCount);
        }

public static extern JobHandle ScheduleParallelFor(ref JobScheduleParameters parameters, int arrayLength, int innerloopBatchCount);

C++中的逆向代码如下,

JobsUtility_ScheduleParallelFor_Injected_m0F7C3AB0415EF402D7242BE6FA82F2ACBC899360

Unity.Jobs.LowLevel.Unsafe.JobsUtility::ScheduleParallelFor_Injected(Unity.Jobs.LowLevel.Unsafe.JobsUtility/JobScheduleParameters&,System.Int32,System.Int32,Unity.Jobs.JobHandle&)
void __fastcall JobsUtility_CUSTOM_ScheduleParallelFor_Injected(
        struct JobScheduleParameters *a1,
        unsigned int a2,
        unsigned int a3,
        struct JobFence *a4)
{
  ...
  v16 = (const void *)ScheduleManagedJobParallelFor(v10, a1, a2, a3, v8);
  ...
}

ScheduleManagedJobParallelFor是非常重要的函数

_QWORD *__fastcall ScheduleManagedJobParallelFor(
        _QWORD *a1,
        __int64 a2,
        unsigned int a3,
        int a4,
        struct ScriptingExceptionPtr *a5)
{
  __int64 *v5; // rdi
  __int64 i; // rcx
  __int64 v8; // [rsp+0h] [rbp-C08h] BYREF
  char v9[2616]; // [rsp+50h] [rbp-BB8h] BYREF
  void *v10; // [rsp+A88h] [rbp-180h] BYREF
  char v11[64]; // [rsp+AA8h] [rbp-160h] BYREF
  char v12[48]; // [rsp+AE8h] [rbp-120h] BYREF
  _QWORD v13[3]; // [rsp+B18h] [rbp-F0h] BYREF
  char v14[12]; // [rsp+B34h] [rbp-D4h] BYREF
  char v15[16]; // [rsp+B40h] [rbp-C8h] BYREF
  __int64 v16; // [rsp+B50h] [rbp-B8h] BYREF
  int v17; // [rsp+B58h] [rbp-B0h]
  __int64 v18; // [rsp+B60h] [rbp-A8h] BYREF
  int v19; // [rsp+B68h] [rbp-A0h]
  char v20[16]; // [rsp+B70h] [rbp-98h] BYREF
  struct ScriptingExceptionPtr *v21; // [rsp+B80h] [rbp-88h]
  _QWORD *v22; // [rsp+B90h] [rbp-78h]
  _QWORD *v23; // [rsp+B98h] [rbp-70h]
  void *v24; // [rsp+BA0h] [rbp-68h]
  int v25; // [rsp+BA8h] [rbp-60h]
  JobBatchDispatcher *v26; // [rsp+BB0h] [rbp-58h]
  __int64 *v27; // [rsp+BB8h] [rbp-50h]
  __int64 v28; // [rsp+BC0h] [rbp-48h]
  __int64 *v29; // [rsp+BC8h] [rbp-40h]
  __int64 *v30; // [rsp+BD0h] [rbp-38h]
  _QWORD *v31; // [rsp+BD8h] [rbp-30h]
  BOOL v32; // [rsp+BE0h] [rbp-28h]
  BOOL v34; // [rsp+BE8h] [rbp-20h]

  v5 = &v8;
  for ( i = 766i64; i; --i )
  {
    *(_DWORD *)v5 = -858993460;
    v5 = (__int64 *)((char *)v5 + 4);
  }
  if ( a3 || (unsigned __int8)RequiresCleanupBuffers(*(_QWORD *)(a2 + 24)) )
  {
    Validate_Before_Schedule((const struct JobScheduleParameters *)a2, a3, 0i64, a5);
    v21 = a5;
    v32 = *(_QWORD *)a5 != 0i64;
    if ( v32 || *((_QWORD *)a5 + 1) )
    {
      v22 = a1;
      *a1 = 0i64;
      *((_DWORD *)v22 + 2) = 0;
      return a1;
    }
    else
    {
      BatchAllocator::BatchAllocator((BatchAllocator *)v9);
      AllocateManagedJobData(v9, &v10, a2, 0i64);
      AllocateWorkStealingRange((struct BatchAllocator *)v9, a3, a4, (struct WorkStealingAllocationData *)v11, -1);
      qmemcpy(v14, (const void *)GetJobAllocator(v15, a2), sizeof(v14));
      qmemcpy(v12, v14, 0xCui64);
      BatchAllocator::Commit((BatchAllocator *)v9, (const struct MemLabelId *)v12);
      qmemcpy(v20, v12, 0xCui64);
      InitializeManagedJobData((_DWORD)v10, a2, 0, 0, (__int64)v20);
      InitializeWorkStealingRange(
        (const struct WorkStealingAllocationData *)v11,
        (struct WorkStealingRange *)((char *)v10 + 48));
      if ( *(_DWORD *)(a2 + 16) == 1 )
      {
        v23 = v13;
        v13[0] = 0i64;
        LODWORD(v13[1]) = 0;
        v25 = *((_DWORD *)v10 + 13);
        v24 = v10;
        v26 = gBatchScheduler;
        JobBatchDispatcher::ScheduleJobForEachInternal(
          gBatchScheduler,
          (struct JobFence *)v13,
          ForwardJobForEachToManaged,
          v10,
          v25,
          ForwardJobForEachCleanup,
          (const struct JobFence *)a2);
        Validate_After_Schedule((const struct JobFence *)v13, (const struct JobScheduleParameters *)a2);
        qmemcpy(a1, v13, 0x10ui64);
        return a1;
      }
      else
      {
        if ( *((_DWORD *)v10 + 13) != 1
          && AssertImplementation(
               0,
               "C:\\buildslave\\unity\\build\\Runtime/Jobs/ScriptBindings/JobsBindings.cpp",
               895,
               -1,
               "Assertion failed on expression: 'jobData->range.numJobs == 1'")
          && (unsigned __int8)Baselib_Debug_IsDebuggerAttached() )
        {
          __debugbreak();
        }
        v27 = &v16;
        v16 = 0i64;
        v17 = 0;
        v29 = &v16;
        v28 = a2;
        v34 = !*(_QWORD *)a2 && *(_DWORD *)(v28 + 8) == *((_DWORD *)v29 + 2);
        if ( !v34
          && AssertImplementation(
               0,
               "C:\\buildslave\\unity\\build\\Runtime/Jobs/ScriptBindings/JobsBindings.cpp",
               896,
               -1,
               "Assertion failed on expression: 'jobParameters.dependency == JobFence()'") )
        {
          if ( (unsigned __int8)Baselib_Debug_IsDebuggerAttached() )
            __debugbreak();
        }
        v30 = &v18;
        v18 = 0i64;
        v19 = 0;
        Validate_After_Schedule((const struct JobFence *)&v18, (const struct JobScheduleParameters *)a2);
        ForwardJobForEachToManaged(v10, 0);
        ForwardJobForEachCleanup(v10);
        v31 = a1;
        *a1 = 0i64;
        *((_DWORD *)v31 + 2) = 0;
        return a1;
      }
    }
  }
  else
  {
    qmemcpy(a1, (const void *)a2, 0x10ui64);
    return a1;
  }
}

JobBatchDispatcher::ScheduleJobForEachInternal这个函数很关键,再SRP原理初探我也提到过.是Unity大多数多线程操作的调用入口.

看重要的回调函数ForwardJobForEachToManaged

void __fastcall ForwardJobForEachToManaged(void *a1, int a2)
{
  __int64 *v2; // rdi
  __int64 i; // rcx
  __int64 v4; // [rsp+0h] [rbp-68h] BYREF
  void (__stdcall *NativeJobFunction)(void *, void *, void *, void *, int); // [rsp+20h] [rbp-48h]
  struct profiling::Marker *v6; // [rsp+38h] [rbp-30h]
  const struct JobReflectionData *v7; // [rsp+48h] [rbp-20h]
  struct profiling::Marker *v8; // [rsp+50h] [rbp-18h]
  __int64 v9; // [rsp+58h] [rbp-10h]

  v2 = &v4;
  for ( i = 24i64; i; --i )
  {
    *(_DWORD *)v2 = -858993460;
    v2 = (__int64 *)((char *)v2 + 4);
  }
  v9 = -2i64;
  NativeJobFunction = GetNativeJobFunction(*(const struct JobReflectionData **)a1, 0);
  v7 = *(const struct JobReflectionData **)a1;
  if ( NativeJobFunction )
    v8 = (struct profiling::Marker *)*((_QWORD *)v7 + 42);
  else
    v8 = (struct profiling::Marker *)*((_QWORD *)v7 + 43);
  v6 = v8;
  profiler_begin(v8);
  ExecuteJobCopyData(
    (const void **)a1,
    (void (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD, _DWORD))NativeJobFunction,
    a2);
  profiler_end(v6);
}

继续向下深入可以看到

ExecuteJobCopyData
	ExecuteJob_0
		ScriptingInvocation::Invoke
			scripting_method_invoke
				il2cpp_runtime_invoke
					gameassembly.il2cpp::vm::Runtime::Invoke
						gameassembly.ExecuteJobFunction_Invoke
							gameassembly._MyJob_Execute

最终会调用gameassembly._MyJob_Execute就是原来脚本中

总结:

整个Unity JobSystem调用过程

job.Schedule//gameassembly.dll
JobsUtility.ScheduleParallelFor
JobsUtility_CUSTOM_ScheduleParallelFor_Injected//来到UnityPlayer.dll
ScheduleManagedJobParallelFor
JobBatchDispatcher::ScheduleJobForEachInternal(//设置线程回调函数
          gBatchScheduler,
          (struct JobFence *)v13,
          ForwardJobForEachToManaged,
          v10,
          v25,
          ForwardJobForEachCleanup,
          (const struct JobFence *)a2);
ExecuteJobCopyData
ExecuteJob_0
ScriptingInvocation::Invoke
scripting_method_invoke
il2cpp_runtime_invoke//回到gameassembly.dll
gameassembly.il2cpp::vm::Runtime::Invoke
gameassembly.ExecuteJobFunction_Invoke
gameassembly._MyJob_Execute//最后才是执行回调函数

这就是整个job.execute函数的执行大致过程,很多细节还要进一步探索.

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Unity-debugging-2018.x.zip是一个Unity版本相关的调试工具包,其中包含了诸如埋点工具和调试插件等功能丰富的工具。这些工具可以帮助Unity开发者在开发自己的游戏时快速、准确地定位和修复代码中的问题,提高了游戏的开发效率和质量。 这个工具包中最值得注意的一点是它的兼容性。它可以与Unity 2018中的许多不同版本一起使用,这意味着无论开发者使用哪个具体版本的Unity,都可以使用这个工具包进行调试。这为开发过程中遇到的问题提供了更为广泛和全面的支持,从而更好地满足了不同开发者的需求和要求。 另外,这个工具包还有一个很不错的特色,就是它的易用性。它提供了直观和易于操作的界面,即使是那些对调试工具很不熟悉的开发者也可以使用它。开发者可以通过它直接在Unity编辑器中观察代码执行过程中的变化,非常方便。 总的来说,Unity-debugging-2018.x.zip是一个非常实用和友好的Unity调试工具包,可以帮助Unity开发者更快速、高效地开发自己的游戏。 ### 回答2: unity-debugging-2018.x.zip是Unity引擎中用于调试的工具包。在程序开发的过程中,会出现各种各样的问题,而调试是解决这些问题的重要手段之一。Unity-debugging-2018.x.zip提供了一系列工具和功能,帮助程序员定位和解决问题。 Unity-debugging-2018.x.zip中包含了各种调试工具,例如调试器、内存分析器、性能分析器等等。这些工具可以帮助开发者监控程序运行的状态,包括内存使用、CPU使用、函数运行时间等等。通过这些信息,开发者可以找到程序中可能存在的性能问题,并对其进行优化。 同时,Unity-debugging-2018.x.zip还提供了调试器,帮助开发者调试程序。开发者可以在调试器中设置断点,一步一步地执行程序,查看变量和函数调用的情况。通过调试器,开发者可以快速定位程序中的错误,减少排错的时间。 总之,Unity-debugging-2018.x.zip是Unity开发中不可或缺的工具包。它可以帮助开发者定位和解决问题,提高程序的稳定性和性能,为游戏开发提供强有力的支持。 ### 回答3: unity-debugging-2018.x.zip是一个用于Unity引擎调试的文件。Unity是一款流行的游戏开发引擎,但在游戏开发过程中难免会遇到各种问题,例如程序崩溃、游戏运行异常等等。此时就需要进行调试。Unity-debugging-2018.x.zip文件中包含了一系列调试工具,可用于分析和诊断Unity游戏/应用程序的问题。其中包括了Unity自带的Profiler(性能分析器)、Debug.Log、断点调试、MonoDevelop等工具,这些工具可以帮助开发者查找问题所在,快速调试程序。一个好的调试工具不仅能帮助开发者快速找到问题,还能提高开发效率,使开发工作更加顺利。总之,Unity-debugging-2018.x.zip文件是Unity调试工具的集合,为开发者解决问题提供了极大的帮助,也是一个值得推广和使用的工具。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值