Support BOUT_FOR_RAJA GPU field operators#3078
Conversation
ggeorgakoudis
commented
Feb 25, 2025
- Generate BOUT_FOR_RAJA code for GPU execution of field operators
| /// by calling CoordinatesAccessor::clear() | ||
| struct CoordinatesAccessor { | ||
| CoordinatesAccessor() = delete; | ||
| CoordinatesAccessor() {} |
There was a problem hiding this comment.
warning: constructor does not initialize these fields: data, mesh_nz [cppcoreguidelines-pro-type-member-init]
include/bout/coordinates_accessor.hxx:87:
- BoutReal* data;
- int mesh_nz; ///< For converting from 3D to 2D index
+ BoutReal* data{};
+ int mesh_nz{}; ///< For converting from 3D to 2D index| ddt = BoutRealArray{&(f.timeDeriv()->operator()(0, 0, 0))}; | ||
| } | ||
|
|
||
| explicit FieldAccessor(const FieldType& f) : FieldAccessor(const_cast<FieldType&>(f)) {} |
There was a problem hiding this comment.
warning: do not use const_cast to remove const qualifier [cppcoreguidelines-pro-type-const-cast]
explicit FieldAccessor(const FieldType& f) : FieldAccessor(const_cast<FieldType&>(f)) {}
^| /// so fa[i] is equivalent to fa.data[i]. | ||
| /// | ||
| BOUT_HOST_DEVICE inline const BoutReal& operator[](int ind) const { return data[ind]; } | ||
| BOUT_HOST_DEVICE inline BoutReal& operator[](int ind) { return data[ind]; } |
There was a problem hiding this comment.
warning: function 'operator[]' has inline specifier but is implicitly inlined [readability-redundant-inline-specifier]
| BOUT_HOST_DEVICE inline BoutReal& operator[](int ind) { return data[ind]; } | |
| BOUT_HOST_DEVICE BoutReal& operator[](int ind) { return data[ind]; } |
| BOUT_HOST_DEVICE inline const BoutReal& operator[](const Ind3D& ind) const { | ||
| return data[ind.ind]; | ||
| } | ||
| BOUT_HOST_DEVICE inline BoutReal& operator[](const Ind3D& ind) { return data[ind.ind]; } |
There was a problem hiding this comment.
warning: function 'operator[]' has inline specifier but is implicitly inlined [readability-redundant-inline-specifier]
| BOUT_HOST_DEVICE inline BoutReal& operator[](const Ind3D& ind) { return data[ind.ind]; } | |
| BOUT_HOST_DEVICE BoutReal& operator[](const Ind3D& ind) { return data[ind.ind]; } |
| explicit FieldPerpAccessor(const FieldPerp& f) { | ||
| ASSERT0(f.isAllocated()); | ||
|
|
||
| data = BoutRealArray{const_cast<BoutReal*>(&f(0, 0, 0))}; |
There was a problem hiding this comment.
warning: do not use const_cast to remove const qualifier [cppcoreguidelines-pro-type-const-cast]
data = BoutRealArray{const_cast<BoutReal*>(&f(0, 0, 0))};
^There was a problem hiding this comment.
Why do we need to take a const field?
If we would take a FieldPerp, we would not need the const_cast.
Do we need a ConstFieldPerpAccessor?
| return {(indPerp.ind - jz) * LocalNy + LocalNz * jy + jz, LocalNy, LocalNz}; | ||
| } | ||
|
|
||
| BOUT_HOST_DEVICE int flatIndPerpto3D(const int& flatIndPerp, const int nz, int jy = 0) const { |
There was a problem hiding this comment.
warning: no header providing "BOUT_HOST_DEVICE" is directly included [misc-include-cleaner]
include/bout/mesh.hxx:40:
- class Mesh;
+ #include "bout/build_config.hxx"
+ class Mesh;| } | ||
|
|
||
| BOUT_HOST_DEVICE int flatIndPerpto3D(const int& flatIndPerp, const int nz, int jy = 0) const { | ||
| int jz = flatIndPerp % nz; |
There was a problem hiding this comment.
warning: variable 'jz' of type 'int' can be declared 'const' [misc-const-correctness]
| int jz = flatIndPerp % nz; | |
| int const jz = flatIndPerp % nz; |
There was a problem hiding this comment.
warning: variable 'jz' of type 'int' can be declared 'const' [misc-const-correctness]
| int jz = flatIndPerp % nz; | |
| const int jz = flatIndPerp % nz; |
We generally put const in front ...
| // Uses the symbol to look up the corresponding Offset | ||
| #define COPY_STRIPE1(symbol) \ | ||
| data[stripe_size * ind.ind + static_cast<int>(Offset::symbol)] = coords->symbol[ind]; | ||
| #define COPY_STRIPE1(symbol) \ |
There was a problem hiding this comment.
warning: function-like macro 'COPY_STRIPE1' used; consider a 'constexpr' template function [cppcoreguidelines-macro-usage]
#define COPY_STRIPE1(symbol) \
^| coords->Bxy.ydown()[ind]; | ||
| if (coords->Bxy.isAllocated()) { | ||
| data[stripe_size * ind.ind + static_cast<int>(Offset::B)] = coords->Bxy[ind]; | ||
| if (coords->Bxy.yup().isAllocated()) |
There was a problem hiding this comment.
warning: statement should be inside braces [readability-braces-around-statements]
| if (coords->Bxy.yup().isAllocated()) | |
| if (coords->Bxy.yup().isAllocated()) { |
src/mesh/coordinates_accessor.cxx:61:
- coords->Bxy.yup()[ind];
+ coords->Bxy.yup()[ind];
+ }There was a problem hiding this comment.
please always use braces for if statements!
| if (coords->Bxy.yup().isAllocated()) | ||
| data[stripe_size * ind.ind + static_cast<int>(Offset::Byup)] = | ||
| coords->Bxy.yup()[ind]; | ||
| if (coords->Bxy.ydown().isAllocated()) |
There was a problem hiding this comment.
warning: statement should be inside braces [readability-braces-around-statements]
| if (coords->Bxy.ydown().isAllocated()) | |
| if (coords->Bxy.ydown().isAllocated()) { |
src/mesh/coordinates_accessor.cxx:64:
- coords->Bxy.ydown()[ind];
+ coords->Bxy.ydown()[ind];
+ }
dschwoerer
left a comment
There was a problem hiding this comment.
This PR roughly doubles the lines of gen_fieldops.jinja - making it very hard to read.
I think we should either manage to move more to the python file, or split it into two files, which of course makes it more tricky to keep in sync, but might be the lesser of two evils ...
Also look at the clang-tidy comments.
| explicit FieldPerpAccessor(const FieldPerp& f) { | ||
| ASSERT0(f.isAllocated()); | ||
|
|
||
| data = BoutRealArray{const_cast<BoutReal*>(&f(0, 0, 0))}; |
There was a problem hiding this comment.
Why do we need to take a const field?
If we would take a FieldPerp, we would not need the const_cast.
Do we need a ConstFieldPerpAccessor?
| } | ||
|
|
||
| BOUT_HOST_DEVICE int flatIndPerpto3D(const int& flatIndPerp, const int nz, int jy = 0) const { | ||
| int jz = flatIndPerp % nz; |
There was a problem hiding this comment.
warning: variable 'jz' of type 'int' can be declared 'const' [misc-const-correctness]
| int jz = flatIndPerp % nz; | |
| const int jz = flatIndPerp % nz; |
We generally put const in front ...
| if (BOUT_ENABLE_RAJA) | ||
| set(GEN_LOOP_EXEC "raja") | ||
| elseif (BOUT_ENABLE_OPENMP) | ||
| set(GEN_LOOP_EXEC "openmp") | ||
| else() | ||
| set(GEN_LOOP_EXEC "serial") | ||
| endif() | ||
| add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/field/generated_fieldops.cxx | ||
| COMMAND ${Python3_EXECUTABLE} gen_fieldops.py --filename generated_fieldops.cxx.tmp | ||
| COMMAND ${Python3_EXECUTABLE} gen_fieldops.py --loop-exec ${GEN_LOOP_EXEC} --filename generated_fieldops.cxx.tmp |
There was a problem hiding this comment.
I think it is a bad idea to generate different code, depending on configure option, as we track the generated code, and we want to do this, so users don't have to generate the code.
If we do not want to generate code full of #if's, we could always generate all 3 versions, and track all files in git. Then we can just #if guard the correct C++ code, and compile 2 empty files.
| {% if lhs.field_type == "FieldPerp" %} | ||
| auto {{lhs.name}}_acc = FieldPerpAccessor{ {{lhs.name}} }; | ||
| {% elif lhs.field_type == "BoutReal" %} | ||
| {% else %} | ||
| auto {{lhs.name}}_acc = FieldAccessor({{lhs.name}}); | ||
| {% endif %} |
There was a problem hiding this comment.
| {% if lhs.field_type == "FieldPerp" %} | |
| auto {{lhs.name}}_acc = FieldPerpAccessor{ {{lhs.name}} }; | |
| {% elif lhs.field_type == "BoutReal" %} | |
| {% else %} | |
| auto {{lhs.name}}_acc = FieldAccessor({{lhs.name}}); | |
| {% endif %} | |
| {% if lhs.field_type != "BoutReal" %} | |
| auto {{lhs.name}}_acc = {{ lhs.accessor }}({{lhs.name}}); | |
| {% endif %} |
Although I think this is nicer, as it is shorter, more clear and no copying is required:
| {% if lhs.field_type == "FieldPerp" %} | |
| auto {{lhs.name}}_acc = FieldPerpAccessor{ {{lhs.name}} }; | |
| {% elif lhs.field_type == "BoutReal" %} | |
| {% else %} | |
| auto {{lhs.name}}_acc = FieldAccessor({{lhs.name}}); | |
| {% endif %} | |
| {% if lhs.field_type != "BoutReal" %} | |
| {{ lhs.accessor }} {{lhs.name}}_acc({{lhs.name}}); | |
| {% endif %} |
|
|
||
| {% if (out == "Field3D") and ((lhs == "Field2D") or (rhs =="Field2D")) %} | ||
| {% if (region_loop == "BOUT_FOR_RAJA") %} | ||
| int mesh_nz = {{lhs.name if lhs.field_type != "BoutReal" else rhs.name}}_acc.mesh_nz; |
There was a problem hiding this comment.
| int mesh_nz = {{lhs.name if lhs.field_type != "BoutReal" else rhs.name}}_acc.mesh_nz; | |
| const int mesh_nz = {{lhs.name if lhs.field_type != "BoutReal" else rhs.name}}_acc.mesh_nz; |
| coords->Bxy.ydown()[ind]; | ||
| if (coords->Bxy.isAllocated()) { | ||
| data[stripe_size * ind.ind + static_cast<int>(Offset::B)] = coords->Bxy[ind]; | ||
| if (coords->Bxy.yup().isAllocated()) |
There was a problem hiding this comment.
please always use braces for if statements!
| elseif (BOUT_ENABLE_OPENMP) | ||
| set(GEN_LOOP_EXEC "openmp") | ||
| else() | ||
| set(GEN_LOOP_EXEC "serial") | ||
| endif() |
There was a problem hiding this comment.
Note that this changes the default. We used to always generate BOUT_FOR unless the user explicitly requested BOUT_FOR_SERIAL. I am fairly confident we do not want to change the default.