@AndersDeleuran @RIL,
I ported my Python code for creating a slope map into a C++ DLL. The execution time decreased from 6 sec in Python to only 0.02 sec with the C++ DLL. This means the C++ DLL is 300X faster than Python. So I am very happy I learned how to do this. Being a retired Intel engineer I am not surprised at this result; the Intel CPU I helped design runs at 250 ps per operation (4 GHz frequency) while the Python interpreter runs more like 75 ns per operation at best. Thus using a C++ DLL can provide significant speedup if you are replacing pure Python code with no Rhinoscript calls.
For code with significant Rhinoscript calls, I have found that the System.Threading.Tasks code (as used in the Grasshopper parallel code) can provide up to 6X speedup in the case of generating contours for a mesh. In this case the Python script spends so much time in the Rhino.Geometry.Mesh.CreateContourCurves code, which is outside the bottleneck of the Python interpreter, many calls to CreateContourCurves can be launched in parallel as the Python interpreter is not busy much of the time.
So the combination of C++ DLL’s and System.Threading.Tasks has provided a tremendous improvement to my drone maps software for generating:
Elevation Maps
Slope Maps
Contour Maps
Automatic Identification of Trees
2D Profiles of Terrain
3D Area of Ditches
3D Volume of Piles
Trimming Mesh to Any Curve without any crashes or non-results
These operations now take just a few seconds on meshes with up to 4.4M faces.
@AndersDeleuran what kind of speedup did you see when you ported your solver functions to C++ DLL’s. Also is there a way to call Rhinoscript functions from the C++ DLL? Can you provide details for this?
This is the Python code:
#
# Setup code common to either Python or C++DLL.
#
vertices = meshGeo.Vertices
# Flatten vertices to x,y,z list for use below in constructing flat ctypes float array.
fvertices = vertices.ToFloatArray() # This also speeds up accessing the vertices below.
maxX, maxY = self.data.maxX, self.data.maxY # These are the maximum X and Y of the mesh computed elsewhere.
Xgs = 1./Xbox_size # Xbox_size is 2' for my 4.4M faces mesh.
gsx = int(round(Xgs*maxX))
gsy = int(round(Xgs*maxY))
bins = (gsx+1)*(gsy+1)
count = vertices.Count
cnormals = (ct.c_float * (3*count))()
vvertices = (ct.c_float * (3*count))()
slopePts = (ct.c_float * count)()
bin_slope = (ct.c_float * bins)()
xave = (ct.c_float * bins)()
yave = (ct.c_float * bins)()
zave = (ct.c_float * bins)()
cXgs = ct.c_float(Xgs)
normals = list(meshGeo.Normals) # This speeds up accessing the normals below.
for i in xrange(count):
j = 3*i
j1 = j+1
j2 = j+2
v = normals[i]
cnormals[j] = v.X
cnormals[j1] = v.Y
cnormals[j2] = v.Z
vvertices[j] = fvertices[j]
vvertices[j1] = fvertices[j1]
#
# Python code below was ported to C++ DLL.
#
for i in xrange(bins):
bin_slope[i] = 0.0
xave[i] = 0.0
yave[i] = 0.0
zave[i] = 0.0
for i in xrange(0,3*count,3):
# Get x,y coordinates of vertex.
x,y = vvertices[i],vvertices[i+1]
# Scale x and y to bin units.
ix,iy = int(Xgs*x), int(Xgs*y)
# Get index of bin.
j = iy*gsx+ix
# Vector add normal to bin.
xave[j] = xave[j]+cnormals[i]
yave[j] = yave[j]+cnormals[i+1]
zave[j] = zave[j]+cnormals[i+2]
# Compute slope for each bin.
for i in xrange(bins):
vx,vy,vz = xave[i], yave[i], zave[i]
if vz == 0.0: vz = 1.
bin_slope[i] = abs(100.*sqrt(vx*vx + vy*vy)/vz)
# Compute slope for each vertice.
for i in xrange(0,3*count,3):
# Get x,y coordinates of vertex.
x,y = vvertices[i],vvertices[i+1]
# Scale x and y to bin units.
ix,iy = int(Xgs*x), int(Xgs*y)
# Get index of bin.
j = iy*gsx+ix
# Use bin slope.
slopePts[i // 3] = bin_slope[j]
This is the Python code that calls the C++ DLL:
name = 'C:\Users\Terry\source\\repos\slopes\\x64\Release\\slopes.dll'
so = ct.cdll.LoadLibrary(name)
# DLL runs in 20 ms for 2.2M vertices and 4.4M faces mesh.
so.slopes(ct.byref(cnormals), ct.byref(vvertices), ct.byref(slopePts), ct.byref(xave), ct.byref(yave), ct.byref(zave), ct.byref(bin_slope), cXgs, gsx, gsy, count)
and this is the C++ DLL code:
// slopes.cpp : Computes the average slope at each vertice in a mesh based upon the average slope of its surrounding bin.
#include "stdafx.h"
#include "math.h"
#define DLLEXPORT extern "C" __declspec(dllexport)
DLLEXPORT void slopes(float *normals, float *vvertices, float *slopePts, float *xave, float *yave, float *zave, float *bin_slope, float Xgs, int gsx, int gsy, int count)
{
float x, y, vx, vy, vz, slope;
int ix, iy, j;
int bins = (gsx + 1)*(gsy + 1);
// Zero average and bin_slope arrays.
for (int i = 0; i < bins; ++i)
{
xave[i] = 0.0;
yave[i] = 0.0;
zave[i] = 0.0;
bin_slope[i] = 0.0;
}
// Find average x,y,z components of normal for each bin.
for (int i = 0; i < 3*count; i+=3) # 3X count and step of 3 are needed to stride across flat vvertices list with x,y,z per vertex.
{
// Get x, y coordinates of vertex.
x = vvertices[i];
y = vvertices[i+1];
// Scale x and y to bin units.
ix = (int)(Xgs*x);
iy = (int)(Xgs*y);
// Get index of bin.
j = iy * gsx + ix;
// Vector add normal to bin.
xave[j] = xave[j] + normals[i];
yave[j] = yave[j] + normals[i+1];
zave[j] = zave[j] + normals[i+2];
}
// Compute slope for each bin from average x,y,z components.
for (int i = 0; i < bins; ++i)
{
vx = xave[i];
vy = yave[i];
vz = zave[i];
// Prevent division by zero that happens in rare cases.
if (vz == 0.0) { vz = 1.; }
bin_slope[i] = fabs(100.*sqrt(vx*vx + vy * vy) / vz); // Slope in % grade.
}
// Compute slope for each vertice
for (int i = 0; i < 3*count; i+=3) # 3X count and step of 3 are needed to stride across flat vvertices list with x,y,z per vertex.
{
// Get x, y coordinates of vertex.
x = vvertices[i];
y = vvertices[i+1];
// Scale x and y to bin units.
ix = int(Xgs*x);
iy = int(Xgs*y);
// Get index of bin.
j = iy * gsx + ix;
// Use bin slope average.
slope = bin_slope[j];
if (slope < 1000.)
{
slopePts[i / 3] = slope;
}
else
{
// Limit maximum computed slope to 1000%.
slopePts[i / 3] = 1000.;
}
}
}
Regards,
Terry.