float4 的 NativeArray 如何将其中一个 xyzw 设置为循环中的值?

Posted

技术标签:

【中文标题】float4 的 NativeArray 如何将其中一个 xyzw 设置为循环中的值?【英文标题】:NativeArray of float4 how to set one of the xyzw to a value in loop? 【发布时间】:2021-09-28 08:48:40 【问题描述】:

当循环遍历 float4[] 的 NativeArray,并希望将每个 float4 的“字段”之一(比如说 y)设置为一个值时,这似乎不是立即可行的,因为它是一个临时值。

这个:

  NativeArray<float4> naFloat4s ;
  void Start ( ) 
    naFloat4s = new NativeArray<float4> ( 96000, Allocator.Persistent ) ;
  
  void MakeNoise ( ) 
    for ( int i = 0 ; i < naFloat4s.Length ; i++ ) 
      naFloat4s[i].y = Random.Range ( - 1f, 1f ) ;
    
  

产生关于它是结构的临时值的抱怨,无法设置。

如何以最高效的方式解决这个问题,从而不产生垃圾,并且 NativeArray 和 Burst/Jobs 可以尽最大努力尽快完成数以万计的设置?

注意:此处使用的随机数只是一个示例。假设那里还有其他东西会产生更有趣的东西。

还请注意,执行此操作时,其他值(在本例中为 x、z 和 w)必须保持不变。它们仍然有用,就像它们一样。只需更改整个数组中 float4 中的一个值。

编辑:根据雨果爵士的评论,修正了范围内的浮动。

回应雨果爵士关于 float4 中浮动指针的评论:

通过这样做,我得到了指向单个浮动工作的指针:

      void LoopDemoWithPointerToFloatOfFloat4_NativeArray() 
        int    samples = 2000;
        int    size_T = UnsafeUtility.SizeOf<float4> ( ) ;
        int    size_F = UnsafeUtility.SizeOf<float> ( ) ;
        int    mStepr = size_F * 1 ; // 1 is y's index value in the float4 struct
        IntPtr dstPtr = ( IntPtr )NativeArrayUnsafeUtility
                         .GetUnsafeBufferPointerWithoutChecks ( naFloat4s ) ;
        
        for ( int i = 0 ; i < samples ; i++ ) 
          unsafe 
            float* yOffset =  (float*) (dstPtr + i * size_T + mStepr);
            *yOffset = (float)i ;
          
        
      

还没来得及检查速度,好像很快。

需要创建一个装备来测试各种 StopWatch....

更新的使用示例:

var p = (float4*)noizADSR.GetUnsafePtr (  );
float stepDekay = 1f / dekayLength ;
ptr = (float*)(p + attakFinish); ptr += nID;
j = overlapping;
for ( int i = attakFinish ; i < noizeLength ; i++, j++, ptr += 4 ) 
*ptr = dekayCrv.Evaluate( j * stepDekay) ;

【问题讨论】:

我实际上完全尝试过这个*(pointer + i * size_T + mStepr) = i; ...是的,它可以编译但是你确定它确实改变了数组中的某些东西吗?对我来说,他们都只是留在0 ;) 这就是为什么在我的回答中我开始打印数组^^ 是的,它正在工作。从那以后,我开始使用更有效的版本,在循环外部创建指针,并在之后更新; i++, ptr+=4),这真的很好。 @derHugo 和你一样,我使用不同的打印输出来确保它正常工作。并且正在运行 1000、10000 和 100000 的测试以查看速度差异。正如您所说,速度快了 3 倍以上。今天我正在做一些其他的噪音创造,但一旦我的噪音工作正常,我会重新审视这个。 @derHugo 添加了一些我正在做的事情的 sn-p,以说明为什么它具有双重用途,其中我根据需要在需要的地方插入噪声 ADSR 曲线的更改。 【参考方案1】:

对于任何 struct (= value) 类型,如果它位于数组中,您只能这样做

var value = naFloat4s[i]; 
value.y = Random.Range(-1, 1); 
naFloat4s[i] = value;

The indexer ([i]) 是一个属性,可以返回或采用完整的float4 结构值。

所以如果你(能够)做

naFloat4s[i].y = something;

基本上返回一个float4 并更改返回float4y 组件。但这不会以任何方式改变存储在数组中的值。


更新

指向指针的东西:

我只是做了一个小测试,如果你真的愿意去unsafe,你可以使用指针。后续测试

private unsafe void Test()

    var testIndexer = new NativeArray<float4>(amount, Allocator.Persistent);
    var testPointer = new NativeArray<float4>(amount, Allocator.Persistent);

    Profiler.BeginSample("TEST Indexed");

    for (var i = 0; i < testIndexer.Length; i++)
    
        var value = testIndexer[i];
        value.y = i;
        testIndexer[i] = value;
    

    Profiler.EndSample();

    Profiler.BeginSample("TEST Pointer");
    var pointer = (float4*)testPointer.GetUnsafePtr();

    for (var i = 0; i < testPointer.Length; i++)
    
        (pointer + i)->y = i;
        // or also (seems equally fast - 10ms slower on 10.000 elements)
        // pointer[i].y = i;
        // or also (seems equally fast)
        // (*(pointer + i)).y = i;
    

    Profiler.EndSample();

    for (var i = 0; i < amount; i++)
    
        Debug.Log($"indexed: testIndexer[i].y, pointer: testPointer[i].y");
    

    Debug.Assert(testIndexer.ToArray().SequenceEqual(testPointer.ToArray()));

    testIndexer.Dispose();
    testPointer.Dispose();

已经至少快了三倍,这很可能是因为使用索引器您有更多操作(读取值、存储值、写入值)。

一些基准值:

amount = 100

索引:0.77 毫秒 指针:0.25 毫秒

amount = 1000

索引:3.40 毫秒 指针:0.67 毫秒

amount = 10000

索引:37.39 毫秒 指针:7.70 毫秒

是否可以实际直接写入单个浮点指针我不知道,tbh,但它甚至可能更快是的。

【讨论】:

@Confused 我不确定计算内存中的确切位置并将某些内容直接写入不安全内存是否更快,然后只需将float4 分配给数组中的索引,虽然...... Burst 通常已经很好地优化了这些东西 @Confused for the pointer stuff this 可能会有所帮助(虽然个人不会走这条路)......我想你可以尝试使用类似 *(pointer + 4 * index + 4) = y; 的东西(假设 Y 组件是存储在内存中的 X 之后) @Confused 刚刚使用至少一个 float4* 指针进行了测试.. 这已经快了很多(请参阅更新的答案)!不确定是否可以直接写信给个人float @Confused 确实是 .. 正如我在代码中添加的那样,(pointer + i)-&gt;y = 基本上等于执行 (*(pointer + i)).y =(pointer[i]).y = .. 所有这些或多或少都具有相同的性能(索引器有点少)..但是是的:基本上一个指针知道它的类型,因此pointer + i基本上意味着pointerAddress + i * sizeOfType @Confused 是的,这些开车兜风有时也会发生在我的回答中,有时没有任何关于什么是错误的 cmets(例如here)^^ *** 随机性......我觉得你 ;)

以上是关于float4 的 NativeArray 如何将其中一个 xyzw 设置为循环中的值?的主要内容,如果未能解决你的问题,请参考以下文章

memcpy NativeArray Index to NativeArray Index, Length,怎么办?

NativeArray并行数据合并

如何使用 UnsafeMutableRawPointer 填充数组?

NSLog matrix_float4x4 或 simd::float4x4

float4数据类型

谁能解释一下float4数据类型