Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

GC spikes yielding on AsyncGPUReadbackRequest

Discussion in '2018.1 Beta' started by laurentlavigne, Jan 19, 2018.

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    I'm getting these in the editor only, build doesn't show GC spikes, why is that?
    Each blue spike allocates 2.7MB for some reason, and ever 3 blue spikes I get a gigantor green spike where I guess the GC is flushing the toilets.
    What I'm doing is downloading 600x600xARGBfloat from a compute shader, I think a argbfloat is 4 bytes so that's 1MB
    Are these allocations how asyncgpurequest works? maybe that's where experimental is for. Dev error is another possibility.

     
  2. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    Can you show the code where the allows happen?
     
  3. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    The spike doesn't come from the async function:
    I changed one of the compute buffer stride to sizeof(bool)=1 which isn't allowed even though the microsoft doc shows that bool are ok in a compute shader, there might be some limitation within dx11 that only allows cpu->gpu of %4 ... but I don't see any mention of that in the unity doc so I filed a bug report 989844
    the gc alloc dropped to 1.7MB so this allocation comes from uploading data to the compute shader only.
    600x600 floats (4 bytes)
    600x600 bool (1 byte??)
    = 1,800,000 ... yep so that GC comes from somewhere else

    you're off the hook @JulienF_Unity
     
  4. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Collections;
    5. using UnityEngine.Experimental.Rendering;
    6.  
    7. public class InfluenceCompute : Simulation {
    8.     public ComputeShader shader;
    9.     RenderTexture output;
    10.     [SerializeField] float cellSize=1;
    11.     ComputeBuffer buffer;
    12.     [SerializeField] Material materialViz;
    13.     public DiffusionSettings settings;
    14.  
    15.     Vector2Int size;
    16.     public override void OnEnable()
    17.     {
    18.         base.OnEnable();
    19.         size = new Vector2Int((int)(gridBounds.size.x/cellSize), (int)(gridBounds.size.z/cellSize));
    20.         buffer = new ComputeBuffer(size.x * size.y, 4 * sizeof(float));
    21.  
    22.         output = new RenderTexture(size.x, size.y, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
    23.         output.filterMode = FilterMode.Point;
    24.         output.enableRandomWrite = true;
    25.         output.Create();
    26.         materialViz.SetTexture("_MainTex", output);
    27.  
    28.         StartCoroutine(AsyncExtract());
    29.     }
    30.  
    31.     IEnumerator AsyncExtract()
    32.     {
    33.         while (true)
    34.         {
    35.             var emitterValues = new float[size.x * size.y];
    36.             foreach (var e in settings.emitters)
    37.             {
    38.                 var pos = World2Grid(e.transform.position);
    39.                 emitterValues [pos.x + pos.y * size.x] = e.value;
    40.             }
    41.             ComputeBuffer emittersBuffer = new ComputeBuffer(emitterValues.Length, sizeof(float));
    42.             emittersBuffer.SetData(emitterValues);
    43.  
    44.             var obstalceValues = new float [size.x * size.y];
    45.             foreach (var e in obstacles)
    46.             {
    47.                 var bounds = World2Grid(e.GetBounds());
    48.                 var bX = new Vector2Int(bounds.min.x, bounds.max.x);
    49.                 var bY = new Vector2Int(bounds.min.y, bounds.max.y);
    50.                 for (int x = bX.x; x < bX.y; x++)
    51.                 {
    52.                     for (int y = bY.x; y < bY.y; y++)
    53.                     {
    54.                         obstalceValues[x + y * size.x] = 1;
    55.                     }
    56.                 }
    57.             }
    58.             ComputeBuffer obstaclesBuffer = new ComputeBuffer(obstalceValues.Length, sizeof(float));
    59.             obstaclesBuffer.SetData(obstalceValues);
    60.  
    61.  
    62.             shader.SetBuffer(0, "emitters", emittersBuffer);
    63.             shader.SetBuffer(0, "obstacles", obstaclesBuffer);
    64.             shader.SetTexture(0, "tex", output);
    65.             shader.SetFloat("trail", settings.trail);
    66.             shader.SetFloat("atten", settings.attenuation);
    67.             shader.SetFloat("diffusion", settings.diffusion);
    68.             shader.SetInt("someValue", (int)Time.time);
    69.             shader.Dispatch(0, size.x/ 8, size.y/ 8, 1);
    70.  
    71.             // extract
    72.        
    73.             var request = AsyncGPUReadback.Request(output);
    74.  
    75.             yield return new WaitUntil(() => request.done);
    76.  
    77. //            var dataz = request.GetData<float>();
    78.             yield return null;
    79.             buffer.Dispose();
    80.             emittersBuffer.Dispose();
    81.             obstaclesBuffer.Dispose();
    82.         }
    83.     }
    84.  
    85.     /*
    86.     private void OnGUI()
    87.     {
    88.         int w = Screen.width / 2;
    89.         int h = Screen.height / 2;
    90.         int s = 512;
    91.  
    92.         GUI.DrawTexture(new Rect(w - s / 2, h - s / 2, s, s), output);
    93.     }
    94.     */
    95.  
    96.     void OnDestroy()
    97.     {
    98.         output.Release();
    99.         buffer.Dispose();
    100.     }
    101.  
    102.     //TODO: send a signal if the thing is out of bouds.
    103.     public Vector3Int World2Grid(Vector3 pos)
    104.     {
    105.         var v = new Vector2(gridBounds.max.x - pos.x, -pos.z + gridBounds.max.z) * size / gridBounds.size.z;
    106.         v.x = Mathf.Clamp(v.x, 0, size.x);
    107.         v.y = Mathf.Clamp(v.y, 0, size.y);
    108.         return new Vector3Int((int)v.x, (int) v.y,0); //HACK assumptions assumptions...
    109.     }
    110.  
    111.     public BoundsInt World2Grid(Bounds bounds)
    112.     {
    113.         var b = new BoundsInt(World2Grid(bounds.center), World2Grid(bounds.size));
    114.         return b;
    115.     }
    116.  
    117. }
     
  5. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    The AsyncExtract co-routine allocates memory every iteration, which turns into garbage eventually. Here is what I could spot without profiling it...

    An array is a ReferenceType which data is allocated on the heap, even though if its elements are of ValueType. If the array isn't referenced by anything anymore, like the next loop iteration, it's considered garbage.
    Code (CSharp):
    1. var emitterValues = new float[size.x * size.y];
    ComputeBuffer is a class, thus a ReferenceType, thus allocates memory on the heap.
    Code (CSharp):
    1. ComputeBuffer emittersBuffer = new ComputeBuffer(emitterValues.Length, sizeof(float));
    Code (CSharp):
    1. var obstalceValues = new float [size.x * size.y];
    Code (CSharp):
    1. ComputeBuffer obstaclesBuffer = new ComputeBuffer(obstalceValues.Length, sizeof(float));
    The returned object is also a ReferenceType, thus allocated on the heap and like with the other examples above, if it's not longer referenced, it's considered garbage.
    Code (CSharp):
    1. var request = AsyncGPUReadback.Request(output);
    Not only the new WaitUntil allocates memory, but the use of an anonymous delegate as well.
    Code (CSharp):
    1. yield return new WaitUntil(() => request.done);

    These allocations are most likely (part of) what you see in the profiler as GC.Alloc's.
     
  6. JulienF_Unity

    JulienF_Unity

    Unity Technologies

    Joined:
    Dec 17, 2015
    Posts:
    326
    Async readback does not allocate managed memory at all. You wont have any garbage using it.
    As Peter said, you're doing the allocation every 2/3 frames. Just allocate the arrays and computebuffers once at init and reuse, spikes will disappear.
     
    elbows likes this.
  7. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Look at this beauty!
    I was zeroing out the arrays with a new T which was hitting the GC pretty hard.
    Now I pre allocate a zero array and do CopyTo to clear, it doesn't even show up on the profiler.
    Next I'll take care of those blue spikes with a job.
    So yeah AsyncGPUReadback is super clean, thanks Julien.

     
    JulienF_Unity likes this.