Search Unity

Why does it take so long to get vertices from a mesh?

Discussion in 'Scripting' started by zioth, Sep 16, 2019.

  1. zioth

    zioth

    Joined:
    Jan 9, 2019
    Posts:
    111
    I just spent a while trying to figure out why a 9000-iteration loop over some mesh vertices took over 2 seconds. I eventually discovered it was because I was accessing MeshFilter.mesh.vertices inside the loop. Anyone know why this is so incredibly slow? Even stranger was that if I had 1500 vertices, the loop only took 1/50 of a second.

    Even if "mesh.vertices" copied the entire array, that wouldn't account for a 100x slowdown for only 6x as many vertices. There has to be a O(n^2) or even O(n^3) algorithm in there somewhere.

    With the tiny optimization of pulling MeshFilter.mesh.vertices out of the loop, I was able to instantiate hundreds of prefabs, and still see a performance boost of 200x!
     
    hopetolive likes this.
  2. bobisgod234

    bobisgod234

    Joined:
    Nov 15, 2016
    Posts:
    1,042
    Post the code.

    If you are iterating over all vertices, and copying the array there, then doubling the number of vertices will both double the number of copies, and double the amount to copy.
     
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Look at the docs for mesh.vertices:

    "Returns a copy of the vertex positions or assigns a new vertex positions array"

    So every time you get the verts, you generate a new array, and copy over all of the vertices. Which means that this:

    Code (csharp):
    1. var positionSum = Vector3.zero;
    2. for (int i = 0; i < mesh.vertices.Length; i++) {
    3.     positionSum += mesh.vertices[i];
    4. }
    5. var averagePosition = positionSum / mesh.vertices.Length;
    Creates 2*n+1 copies of the vertices, where n is the number of vertices, while this:

    Code (csharp):
    1. var vertices = mesh.vertices;
    2. var positionSum = Vector3.zero;
    3. for (int i = 0; i < vertices.Length; i++) {
    4.     positionSum += vertices[i];
    5. }
    6. var averagePosition = positionSum / vertices.Length;
    Creates one.

    This is one of those old decisions that Unity made way back which keeps confusing people. There's very good reasons for things to function like this, but having it be behind a .vertices property keeps tricking people into thinking that they're getting a reference to the verts.
     
  4. zioth

    zioth

    Joined:
    Jan 9, 2019
    Posts:
    111
    Ah, that makes much more sense. Once I started calling "mesh.vertices" in a loop, it became an O(n^2) algorithm. They could probably fix this with a mesh.GetVertex() method, but I should have been caching the object outside the loop anyway, so it's not a big deal.
     
  5. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,748
  6. zioth

    zioth

    Joined:
    Jan 9, 2019
    Posts:
    111
  7. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,748
  8. zioth

    zioth

    Joined:
    Jan 9, 2019
    Posts:
    111
  9. zioth

    zioth

    Joined:
    Jan 9, 2019
    Posts:
    111
    Oh, I get it. You can call GetVertices() in a less-frequently-accessed scope (like Start()).
     
  10. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    The mesh data is stored on the c++ engine side, so having a direct reference would be very, very, very hairy, and not at all portable.
     
  11. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,748
    It will copy data to the list you provided instead of allocating memory for new array every time you use .vertices. Yes, the only advantage of this method is what you may allocalte list at start and only ask mesh to refill it instead of creating new one. Copying occurs anyway, it just the matter where to - to the newly allocated array, or to the list you managed to provide.

    I usually use this with static list instance for easy. It works fine until you don't do multithreading meshes generation.
     
  12. zioth

    zioth

    Joined:
    Jan 9, 2019
    Posts:
    111
    I do use multithreading to generate my meshes, and I only fetch vertices once per mesh, so this won't help me, but I'll keep it in mind for future projects. A better approach for me would be to save the vertices array when I first create it. Since I generate the mesh in code, I have complete control over the vertices. No need to copy them at all.
     
  13. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,748
    If you're using your own thread pool, you should be aware there's ThreadStatic attribute for creating static caches at thread level.

    This way it should be even faster, without retreiving vertices array at all.
     
  14. zioth

    zioth

    Joined:
    Jan 9, 2019
    Posts:
    111
    Is it safe in C# to pass an array reference around between threads? The vertices array is created in a worker thread, after which the object containing the array is (or at least should be) garbage collected.
     
  15. zioth

    zioth

    Joined:
    Jan 9, 2019
    Posts:
    111
    Seems to work fine. Thanks for your help!