0

When looping through a NativeArray of float4[], and wanting to set one of the 'fields' of each float4 (let's say y) to a value, it doesn't seem immediately possible because it's a temporary value.

this:

  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 ) ;
    }
  }

Generates the complaint about it being a temporary value of a struct, can't be set.

How is this problem most performantly overcome such that there's no garbage created and the NativeArray and Burst/Jobs can do their utmost to get through this process for tens of thousands of settings as fast as possible?

Note: the random used here is just an example. Presume there's something else there generating something more interesting.

Also note, when doing this, the other values (in this case x, z and w) must remain unchanged. They're still useful, as they are. The need is to change just one of the values in the float4, throughout the array.

Edit: Fixed floats in range, as per Sir Hugo's comment.

In response to comment by sir Hugo regarding pointer to float in float4:

I got the pointer to the individual float working, by doing this:

      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 ;
          }
        }
      }

Haven't had a chance to check the speed, it seems fast.

Need to create a rig to test various with StopWatch....

Updated example of usage:

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) ;
}
Confused
  • 6,048
  • 6
  • 34
  • 75
  • I actually tried exactly this `*(pointer + i * size_T + mStepr) = i;` ... and yes it compiles **but** are you sure it actually does change something in the array? For me they all just stayed `0` ;) That's why in my answer I started to print the arrays out ^^ – derHugo Sep 30 '21 at 07:30
  • Yes, it was working. I've since started using a more efficient version of this, with the pointer created outside the loop, and updated right after ; i++, ptr+=4) and this is working really well. – Confused Sep 30 '21 at 08:01
  • @derHugo and like you, I was using different printouts to make sure it was working. And was running tests of 1000, 10000 and 100000 to see the speed differences. It's as you say, more than 3x faster. Am doing something else with noise creation today, but will be revisiting this once I get my noises working right. – Confused Sep 30 '21 at 08:04
  • @derHugo added a little snippet of what I'm doing with this, to show why it's doubly useful, wherein I'm inserting changes to noise ADSR curves as needed, exactly where needed. – Confused Sep 30 '21 at 08:32

1 Answers1

1

As usual for any struct (= value) type if it is within an array you can only do

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

The indexer ([i]) is a property and either returns or takes a complete float4 struct value.

So if you would (be able to) do

naFloat4s[i].y = something;

you would basically return a float4 and change that returned float4's y component. But this wouldn't in any way alter the value that is stored in the array.


UPDATE

To the pointer thing:

I just made a little test and indeed if you are really willing to go unsafe you could use pointers. Following test

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();
}

already was at least three times faster which is most probably ofcourse due to the fact that using the indexer you have more operations (read a value, store the value, write the value).

Some benchmark values:

  • amount = 100

    • Indexed: 0.77 ms
    • Pointer: 0.25 ms
  • amount = 1000

    • Indexed: 3.40 ms
    • Pointer: 0.67 ms
  • amount = 10000

    • Indexed: 37.39 ms
    • Pointer: 7.70 ms

Whether it is possible to actually directly write to a single float pointer I don't know, tbh, but it might even be faster yes.

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Can I do an intPtr(ofArrayLocation) +index +widthToElement = value? – Confused Sep 28 '21 at 09:04
  • @Confused why would you want to do that if the indexer already takes care of it? – derHugo Sep 28 '21 at 09:06
  • Want this to be as fast as possible. I don't know how the indexer takes care of it. I'm seeing you create a float4 and push the whole float4 back to the array's index in your answer. that's a lot more work. Whereas just pushing a value into the element's location is a single operation, with a bit of luck. – Confused Sep 28 '21 at 09:08
  • @Confused I you want this to be fast do not use `Random` but rather `Unity.Mathematics.Random random` and move this entire thing into a Job using Unity's JobSystem and the [Burst Compiler](https://docs.unity3d.com/Packages/com.unity.burst@0.2/manual/index.html)! – derHugo Sep 28 '21 at 09:11
  • And, yes, this is all going into Jobs and being Bursted, but I want to know how to change a y value as fast as possible, in a float4, and do it that way. As fast as possible. I'm struggling to figure out how to do the pointer thing. – Confused Sep 28 '21 at 09:13
  • 1
    @Confused I'm not sure if calculating the exact position in memory and directly writing something into unsafe memory is all faster then simply assign a `float4` to an index in the array, though ... Burst usually already does a very good job optimizing the shit out off this stuff – derHugo Sep 28 '21 at 09:15
  • As soon as I figure out how to do the intPtr addressing of the y element, I'll begin testing and let you know ;) – Confused Sep 28 '21 at 09:18
  • 1
    @Confused for the pointer stuff [this](https://www.c-sharpcorner.com/article/pointers-and-unsafe-code-in-c-sharp-everything-you-need-to-know/) might help (personally wouldn't go this route though) ... I guess you could try using something like `*(pointer + 4 * index + 4) = y;` (assuming that the Y component is stored after the X in the memory) – derHugo Sep 28 '21 at 09:38
  • I''m having no luck assigning a value to the pointer. No matter what I try, it's not letting me. – Confused Sep 28 '21 at 09:45
  • Argh, needed another caste: ' * ( float* )( dstPtr + i * size_Float4 + size_Float ) = y ;' – Confused Sep 28 '21 at 09:51
  • 1
    @Confused just made a test using at least a `float4*` pointer .. this is already a lot faster (see updated answer)! Not sure if it is possible to actually write to an individual `float` directly – derHugo Sep 28 '21 at 11:05
  • WOWSERS!!! Been eating. Will brew some coffee and attempt to access individual floats inside a float4. I drafted it before food, and Rider liked it, but not sure if it works, yet. THANK YOU!!! Amazing that it's already much faster. Tripping!!! – Confused Sep 28 '21 at 11:59
  • What is this -> magic: (pointer + i)->y ? – Confused Sep 28 '21 at 12:12
  • @Confused this is a dereferencing of the `y` property via the `float4*` pointer ... so basically the same as `array[i].y = value;` just that with pointers it is actually possible ;) – derHugo Sep 28 '21 at 12:49
  • @Confused `I drafted it before food, and Rider liked it, but not sure if it works` .. if you mean that `*( float* )(dstPtr + i * 16 + 4) = y;` .. nope I tried to get it working directly with `float*` .. without success ^^ No comp errors but also nothing changed in the array ... or sometimes Editor crash :'D – derHugo Sep 28 '21 at 12:52
  • @Confused see [Member access operator `->`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/pointer-related-operators#pointer-member-access-operator--) ;) – derHugo Sep 28 '21 at 12:57
  • These the kinds of things that make me feel very ignorant. I had no idea there was member access operators of this sort. See additional info in question, for how I got float access in float4. It's a bit messy, to say the least. But it works! – Confused Sep 28 '21 at 13:23
  • Pointers seem to have some awareness of their size. So if you add 4 to a float4*, it's going up by 64 bytes, not 16 bytes. Similarly, if you add 4 to a float*, it's going up by 16 bytes, not 4. – Confused Sep 28 '21 at 14:04
  • 1
    @Confused yes indeed .. as I added in my code this `(pointer + i)->y =` basically equals doing `(*(pointer + i)).y =` or also `(pointer[i]).y =` .. all these are more or less equally performant (the indexer a bit less) .. but yes to the point: Basically a pointer knows its type and therefore `pointer + i` basically means `pointerAddress + i * sizeOfType` – derHugo Sep 28 '21 at 14:17
  • 1
    @Confused and yeah these drivebys also happen to my answers sometimes without any comments whatsoever about what is wrong (e.g. [here](https://stackoverflow.com/questions/69282112/how-to-do-waituntil-in-async/69284272#69284272)) ^^ Stackoverflow randomness ... I feel ya ;) – derHugo Sep 28 '21 at 14:18
  • Sorry, didn't pick that up the first read through. This was at least part of the reason I was getting crashes. I was going out of the bounds, adding way too much to the pointer each loop cycle. – Confused Sep 28 '21 at 14:18
  • 1
    @Confused this was probably because of my comment `*(pointer + 4 * index + 4) = y;` which was just a wild guess when I still had no idea of pointers at all this morning :D :D – derHugo Sep 28 '21 at 14:20
  • So not only are you amazing with stuff you're experienced in, throughout a morning you can become a guru of *pointerisms. If we ever get to travelling again, I'm buying you so much beer you come back to somewhere near my lowly level. – Confused Sep 28 '21 at 14:23
  • Here's something interesting, I think Unity solved this problem for us, with this: https://docs.unity3d.com/ScriptReference/Unity.Collections.LowLevel.Unsafe.UnsafeUtility.WriteArrayElementWithStride.html – Confused Sep 30 '21 at 07:28