nschloe/pygmsh

gmesh multi-thread support

frli8848 opened this issue · 12 comments

I have tried to make pygmsh/gmsh use more than one thread using arguments like,

msh = pygmsh.generate_mesh(geom, extra_gmsh_arguments=['Mesh.Algorithm3D=10 General.NumThreads=32'])

but gmsh still just use one thread (per node). According to the gmsh docs using Mesh.Algorithm3D=10 ("10" is the 3D HXT algorithm which has OpenMP support [see http://gmsh.info/doc/texinfo/gmsh.html]). The only way I could get gmsh to use multiple threads for 3D meshing was to supply the -nt flag. Below is a small patch which seems to work:

diff --git a/pygmsh/helpers.py b/pygmsh/helpers.py
index 5b86906..bf0da79 100644
--- a/pygmsh/helpers.py
+++ b/pygmsh/helpers.py
@@ -53,6 +53,7 @@ def generate_mesh(  # noqa: C901
     geo_object,
     verbose=True,
     dim=3,
+    num_threads=1,
     prune_vertices=True,
     prune_z_0=False,
     remove_lower_dim_cells=False,
@@ -109,6 +110,8 @@ def generate_mesh(  # noqa: C901
 
     args = [
         f"-{dim}",
+        f"-nt",
+        f"{num_threads}",
         geo_filename,
         "-format",
         mesh_file_type,

This adds a new arg num_threads to generate_mesh so one can do, for example,

msh = pygmsh.generate_mesh(geom, num_threads=32, extra_gmsh_arguments=['Mesh.Algorithm3D=10 General.NumThreads=32'])

Also, by looking at gmsh CMakeLists.txt file one must build gmsh with

-DENABLE_NETGEN=off
-DENABLE_OPENMP=on

for this to work (setting -DENABLE_NETGEN=on enables a defiine which switches off threading). Ideally one should probe gmsh for threading/OpenMP support but I'm not sure how to do this.

You can use the extra_gmsh_arguments argument for that.

OK, how is this done then? For example, I tried

msh = pygmsh.generate_mesh(geom, extra_gmsh_arguments=['-nt 32 Mesh.Algorithm3D=10 General.NumThreads=32 Mesh.MaxNumThreads3D'])

but this fails with:

Error   : Unknown option '-nt 32 Mesh.Algorithm3D=10 General.NumThreads=32 Mesh.MaxNumThreads3D'
Usage: gmsh [options] [files]
Geometry options:
  -0                   Output model, then exit
  -tol value           Set geometrical tolerance
  -match               Match geometries and meshes
Mesh options:

-snip-

Each item must be a separate entry, i.e., ["-nt", "32", ...].

Thanks that works! Are there any plans give the low-level gmsh parameters more descriptive names in pygmsh, like using a single num_threads parameter instead of having to (first find out) and type all the -nt 32, General.NumThreads=32 ... stuff, or name the meshing algorithm, like "HXT" or “Delaunay” instead of just having a number?

Thanks that works! Are there any plans give the low-level gmsh parameters more descriptive names in pygmsh,

No. gmsh has many parameters, sometimes they are added and sometimes removed, and pygmsh is not going to try to keep up with this. Also, explicit is better than implicit.

Hi.
I'm trying to do the same thing. But the above solution seems to be not valid anymore.
pygmsh has been changed a lot after version7.

I tried to change General.NumThreads with gmsh, but does not change.

import pygmsh
import gmsh


with pygmsh.occ.Geometry() as geom:

    geom.characteristic_length_max = 0.1

    # use multithread for mesh creation
    gmsh.option.setNumber("General.NumThreads", 32)
    # use multithread for boolean operation
    gmsh.option.setNumber("Geometry.OCCParallel", 1)
    outer = geom.add_ball([0,0,0], 10)
    inner = geom.add_ball([0,0,0], 3)
    print("general nt: ", gmsh.option.getNumber("General.NumThreads"))
    print("boolean op: ", gmsh.option.getNumber("Geometry.OCCParallel"))
    geom.boolean_difference([outer], [inner])

    mesh = geom.generate_mesh(verbose=True)

However, Geometry.OCCParallel updates correctly.

How can I change General.NumThreads? The timing of changing the option seems important.

I'm trying to do the same thing. But the above solution seems to be not valid anymore.

That's right.

, but does not change.

You did not include the output of your script. For me, it gives

general nt:  32.0
boolean op:  1.0

so it all seems to work correctly.

Thanks for your reply.

I get this output.

general nt:  1.0
boolean op:  1.0

Then, I guess it is not the code's problem.

I found out the reason.
Gmsh with OpenMP enabled is required to use multithreaded meshing.
Details can be found in this issue.

Perfect, thanks for pursuing this.

I have an additional question about multithread performance.
I successfully set General.NumThreads, but it seems like Gmsh keeps using a single CPU.

Screenshot from 2020-12-02 23-34-58

Is it a common situation for using Gmsh, or am I doing something wrong?

I tried to set Mesh.MaxNumThreads1D, but situation is the same.

with pygmsh.occ.Geometry() as geom:

    geom.characteristic_length_max = 0.1

    # use multithread for mesh creation
    gmsh.option.setNumber("General.NumThreads", 8)
    gmsh.option.setNumber("Mesh.MaxNumThreads1D", 8)
    gmsh.option.setNumber("Mesh.MaxNumThreads2D", 8)
    gmsh.option.setNumber("Mesh.MaxNumThreads3D", 8)
    # use multithread for boolean operation
    gmsh.option.setNumber("Geometry.OCCParallel", 1)

    outer = geom.add_ball([0,0,0], 10)
    inner = geom.add_ball([0,0,0], 3)
    geom.boolean_difference([outer], [inner])

    print("general nt: ", gmsh.option.getNumber("General.NumThreads"))
    print("MaxNumThreads1D nt: ", gmsh.option.getNumber("Mesh.MaxNumThreads1D"))
    print("boolean op: ", gmsh.option.getNumber("Geometry.OCCParallel"))

    mesh = geom.generate_mesh(verbose=True)

Any hint would be appreciated.

No idea. Perhaps another question for the gmsh devs.