C#. Ways of improving calculation time of scripted components

Hi everyone,

I don´t have much of a background in scripting and have been slowly trying stuff out.

So here is my question, I have coded a component to iteratively subdivide a Mesh. The thing is that the resulting component is extremely slow. I know it is exponential in every iteration but still after 4 or 5 iterations it becomes practically unusable because it crashes when it really shouldn’t. I know that the way that I wrote is not the best so I am uploading it to see if anyone can give any suggestions as to how to improve its efficency and calculation time.

Thanks in advance.

Scripting in C.gh (7.2 KB)

If you want to keep doing this to the symmetric box, instead of doing it for 6 faces, you can do it with only on the face and array the result. In level 5 you have 18000 vertices, so the code is running this amount of times. it seems to me there is no better way to script this.
Best,

Mahan

It is a pitfall of that amount of exponential geometry as well. Same reason why Weaverbird stops you at 3 subdivision levels (unless you turn it off).

One thing you can try is put each mesh faces subdivision operation into a different thread with a parallel.for loop but I’ve not seen your code to see if that is possible for the type of subdivision you are doing. Not at a computer now but can give it a try later unless someone else beats me to it.

1 Like

Thanks for the reply Mahan, the symmetric box was just something I did to have as an example. But the idea is to be able to apply it to larger non uniform quad meshes, thats why I was trying to improve its efficiency.

Thanks for the reply Michael, I will look into multithreading as it is not something I have done before and see if I get better results.

The thing is that I can then run the result from the C# component I coded through Wb’s Catmull Clark and it runs quite fast even at 3 levels with no problem. This is what gave the clue that there might be something wrong in the logic I coded this, because other components are then able to keep subdividing this mesh at high levels and without much delay.


As seen in the video, This code also works for Triangular Faces.
private void RunScript(Mesh m, int n, double d, double f, ref object A)
{
  m.RebuildNormals();
  for(var l = 0; l < n; l++)
  {
    var meshes = new List<Mesh>();
    var mesh = new Mesh();
    for(var i = 0; i < m.Faces.Count; i++)
      meshes.Add((SubdivideFace(m, i, d, f)));
    mesh.Append(meshes);
    mesh.RebuildNormals();
    m = mesh;
  }
  m.Weld(Math.PI);
  A = m;
}
// <Custom additional code> 
readonly MeshFace[] quad =
{
  new MeshFace(0, 2, 3, 1),
  new MeshFace(2, 4, 5, 3),
  new MeshFace(4, 6, 7, 5),
  new MeshFace(6, 0, 1, 7),
  new MeshFace(1, 3, 5, 7)
};
readonly MeshFace[] tri =
{
  new MeshFace(0, 2, 3, 1),
  new MeshFace(2, 4, 5, 3),
  new MeshFace(4, 0, 1, 5),
  new MeshFace(1, 3, 5)
};
Mesh SubdivideFace(Mesh m, int i, double d, double f)
{
  var mf = m.Faces[i];
  var normal = new Vector3d(m.FaceNormals[i]) * d;
  var center = m.Faces.GetFaceCenter(i) + normal;
  var pts = new List<Point3d>();
  var count = mf.IsQuad ? 4 : 3;
  for(var j = 0; j < count; j++)
  {
    Point3d pt = m.Vertices[mf[j]];
    pts.Add(pt);
    pts.Add(pt + ((center - pt) * f));
  }
  var o = new Mesh();
  o.Vertices.AddVertices(pts);
  o.Faces.AddFaces(mf.IsQuad ? quad : tri);
  return o;
}
// </Custom additional code> 

MeshFractal.gh (19.1 KB)

Parallelization doesn’t seem to make a significant difference here, because the amount of work must be done on each face is so small.


MeshFractal Parallel.gh (18.6 KB)
P.S The video is recorded in realtime.

5 Likes

Parallelization doesn’t seem to make a significant difference here, because the amount of work must be done on each face is so small.

Yeap, seeing your code that makes sense. I haven’t looked at the original code yet so I wasn’t sure.

1 Like

Probably faster. Undoubtedly the overhead of parallelism makes parallel here not an ideal solution.

Performance report of 4 object(s):
PancakeDev is required for detailed timing and monitoring.

Computation time of the selected:
    00:00:00.1792800
Computation time of the document:
    00:00:00.1815925
Proportion:
    98.73%

Repeated:
    28 times

Bottleneck (individual):
    27.86% 0:00:00:00.0499499 (-11.01% ~ +19.96%) Benchmark
    24.74% 0:00:00:00.0443614 (-16.51% ~ +38.73%) Parallel ConcurrentBag
    24.06% 0:00:00:00.0431277 (-13.54% ~ +36.80%) Parallel PLINQ
    23.34% 0:00:00:00.0418410 (-12.50% ~ +46.36%) Parallel ThreadLocal

Bottleneck (grouped):
    100.00% 0:00:00:00.1792800 C# Script
using System;
using System.Collections;
using System.Collections.Generic;

using Rhino;
using Rhino.Geometry;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;

using System.Threading.Tasks;
using System.Threading;
using System.Linq;
using Rhino.Geometry.Collections;

/// <summary>
/// This class will be instantiated on demand by the Script component.
/// </summary>
public class Script_Instance : GH_ScriptInstance
{
  /// <summary>
  /// This procedure contains the user code. Input parameters are provided as regular arguments,
  /// Output parameters as ref arguments. You don't have to assign output parameters,
  /// they will have a default value.
  /// </summary>
  private void RunScript(Mesh m, int n, double d, double f, ref object A)
  {
    global_d = d;
    global_f = f;

    for(var l = 0; l < n; l++)
    {
      var mesh = new Mesh();

      faces = m.Faces.ToIntArray(false);
      vertices = m.Vertices.ToPoint3dArray();

      mesh.Append(ParallelEnumerable.Range(0, faces.Length >> 2).Select(SubdivideFace));

      mesh.RebuildNormals();
      m = mesh;
    }
    m.Weld(Math.PI);
    A = m;
  }

  // <Custom additional code> 
  private static int[] faces = null;
  private static Point3d[] vertices = null;

  private static double global_d = 0;
  private static double global_f = 0;

  private static ThreadLocal<Point3d[]> tl_pts4 = new ThreadLocal<Point3d[]>(() => new Point3d[8]);
  private static ThreadLocal<Point3d[]> tl_pts3 = new ThreadLocal<Point3d[]>(() => new Point3d[6]);

  static readonly MeshFace[] quad =
    {
    new MeshFace(0, 2, 3, 1),
    new MeshFace(2, 4, 5, 3),
    new MeshFace(4, 6, 7, 5),
    new MeshFace(6, 0, 1, 7),
    new MeshFace(1, 3, 5, 7)
    };
  static readonly MeshFace[] tri =
    {
    new MeshFace(0, 2, 3, 1),
    new MeshFace(2, 4, 5, 3),
    new MeshFace(4, 0, 1, 5),
    new MeshFace(1, 3, 5)
    };
  Mesh SubdivideFace(int i)
  {
    Point3d ptr1 = vertices[faces[i << 2]];
    Point3d ptr2 = vertices[faces[(i << 2) + 1]];
    Point3d ptr3 = vertices[faces[(i << 2) + 2]];
    Point3d ptr4 = vertices[faces[(i << 2) + 3]];

    var orgNormal = Vector3d.CrossProduct(ptr2 - ptr1, ptr3 - ptr1);
    orgNormal.Unitize();

    var normal = orgNormal * global_d;


    if(ptr3 != ptr4)
    {
      var center = normal + (ptr1 + ptr2 + ptr3 + ptr4) / 4;

      var pts = tl_pts4.Value;

      pts[0] = ptr1;
      pts[1] = ptr1 + ((center - ptr1) * global_f);

      pts[2] = ptr2;
      pts[3] = ptr2 + ((center - ptr2) * global_f);

      pts[4] = ptr3;
      pts[5] = ptr3 + ((center - ptr3) * global_f);

      pts[6] = ptr4;
      pts[7] = ptr4 + ((center - ptr4) * global_f);

      var o = new Mesh();
      o.Vertices.AddVertices(pts);
      o.Faces.AddFaces(quad);
      return o;
    }else{
      var center = normal + (ptr1 + ptr2 + ptr3) / 3;

      var pts = tl_pts3.Value;

      pts[0] = ptr1;
      pts[1] = ptr1 + ((center - ptr1) * global_f);

      pts[2] = ptr2;
      pts[3] = ptr2 + ((center - ptr2) * global_f);

      pts[4] = ptr3;
      pts[5] = ptr3 + ((center - ptr3) * global_f);

      var o = new Mesh();
      o.Vertices.AddVertices(pts);
      o.Faces.AddFaces(tri);
      return o;
    }
  }
}

Thank you Mahdiayr for the reply this great!

Even if there is no significant difference using parallel computing your code still works much faster than mine and can even go up to 8 iterations whereas mine can´t go beyond 4. This is the type on insight I was trying to get, your code is much shorter and elocuent than what I did. Could you shed a little bit of light as to how you approached it?

Espacially this bit, how does it work exactly? I have never come across this syntax before.

readonly MeshFace quad =
{
new MeshFace(0, 2, 3, 1),
new MeshFace(2, 4, 5, 3),
new MeshFace(4, 6, 7, 5),
new MeshFace(6, 0, 1, 7),
new MeshFace(1, 3, 5, 7)
};
readonly MeshFace tri =
{
new MeshFace(0, 2, 3, 1),
new MeshFace(2, 4, 5, 3),
new MeshFace(4, 0, 1, 5),
new MeshFace(1, 3, 5)
};

Don’t know if you seen this, but as it is recursion it remind me this


1 Like