Add compiler timing diagnostics to mjsCompiler, printed by compile.cc
For example, `compile mujoco_menagerie/robotis_op3/scene.xml` now outputs
```
Compile 1 (cold cache):
total: 317.2 ms
assets: 284.8 ms (wall clock)
load: 616.1 ms
hull: 26.4 ms
poly: 137.8 ms
inert: 177.8 ms
bvh: 568.2 ms
octr: 1.6 ms
tex: 25.3 ms
other: 32.4 ms
Compile 2 (warm cache):
total: 79.9 ms
assets: 54.5 ms (wall clock)
load: 888.5 ms
hull: 0.0 ms
poly: 0.0 ms
inert: 0.0 ms
bvh: 0.0 ms
octr: 0.0 ms
tex: 21.4 ms
other: 25.3 ms
```
PiperOrigin-RevId: 917850214
Change-Id: Iaec86230bec0faf2e47820e20cbff61de5b2621e
This commit is contained in:
committed by
Copybara-Service
parent
f712eed4ce
commit
bdf00966f9
@@ -828,6 +828,18 @@ Type of built-in procedural mesh.
|
||||
.. mujoco-include:: mjtMeshBuiltin
|
||||
|
||||
|
||||
.. _mjtCTimer:
|
||||
|
||||
mjtCTimer
|
||||
~~~~~~~~~
|
||||
|
||||
Compiler timing categories, used in :ref:`mjs_getTimer`. Top-level timers (``TOTAL``, ``ASSETS``) measure wall-clock
|
||||
time. Asset sub-timers measure CPU time summed across all assets; with multi-threaded compilation their sum can exceed
|
||||
the ``ASSETS`` wall-clock time.
|
||||
|
||||
.. mujoco-include:: mjtCTimer
|
||||
|
||||
|
||||
.. _tyPluginEnums:
|
||||
|
||||
Plugins
|
||||
|
||||
@@ -2079,6 +2079,15 @@ Write [datetime, type: message] to MUJOCO_LOG.TXT.
|
||||
|
||||
Get compiler error message from spec.
|
||||
|
||||
.. _mjs_getTimer:
|
||||
|
||||
`mjs_getTimer <#mjs_getTimer>`__
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. mujoco-include:: mjs_getTimer
|
||||
|
||||
Get compiler timing diagnostics from spec, returns pointer to array of size mjNCTIMER.
|
||||
|
||||
.. _mjs_isWarning:
|
||||
|
||||
`mjs_isWarning <#mjs_isWarning>`__
|
||||
|
||||
@@ -17,6 +17,10 @@ General
|
||||
release, now uses a fixed seed. The previous implementation seeded with ``mjData.time``, which introduced subtle yet
|
||||
undesirable time dependence.
|
||||
- Flexes are now allowed to sleep, with the exception of completely passive (constraint-free) flexes.
|
||||
- Added compiler timing diagnostics via the new :ref:`mjtCTimer` enum and the :ref:`mjs_getTimer` C API. After
|
||||
:ref:`mj_compile`, per-category timings (total, assets, mesh loading, convex hull, normals, inertia, BVH, octree,
|
||||
textures) are available via ``mjs_getTimer(spec)``. The :ref:`compile<saCompile>` sample prints a detailed timing
|
||||
breakdown when run without an output file.
|
||||
|
||||
.. admonition:: Breaking API changes
|
||||
:class: attention
|
||||
|
||||
@@ -1965,6 +1965,22 @@ typedef enum mjtOrientation_ { // type of orientation specifier
|
||||
mjORIENTATION_ZAXIS, // z axis (minimal rotation)
|
||||
mjORIENTATION_EULER, // Euler angles
|
||||
} mjtOrientation;
|
||||
typedef enum mjtCTimer_ { // compiler timing categories
|
||||
// top-level timers (wall-clock)
|
||||
mjCTIMER_TOTAL = 0, // total compile time
|
||||
mjCTIMER_ASSETS, // asset compilation
|
||||
|
||||
// asset sub-timers (CPU time, summed across all assets)
|
||||
mjCTIMER_TEXTURE, // textures
|
||||
mjCTIMER_MESH_LOAD, // mesh: file loading
|
||||
mjCTIMER_MESH_HULL, // mesh: convex hull
|
||||
mjCTIMER_MESH_POLYGON, // mesh: normals and polygons
|
||||
mjCTIMER_MESH_INERTIA, // mesh: volume, CoM, inertia
|
||||
mjCTIMER_MESH_BVH, // mesh: bounding volume hierarchy
|
||||
mjCTIMER_MESH_OCTREE, // mesh: octree and SDF
|
||||
|
||||
mjNCTIMER // number of compiler timers
|
||||
} mjtCTimer;
|
||||
typedef struct mjsElement_ { // element type, do not modify
|
||||
mjtObj elemtype; // element type
|
||||
uint64_t signature; // compilation signature
|
||||
@@ -3471,6 +3487,7 @@ void mju_free(void* ptr);
|
||||
void mj_warning(mjData* d, int warning, int info);
|
||||
void mju_writeLog(const char* type, const char* msg);
|
||||
const char* mjs_getError(mjSpec* s);
|
||||
const double* mjs_getTimer(mjSpec* s);
|
||||
int mjs_isWarning(mjSpec* s);
|
||||
void mju_zero3(mjtNum res[3]);
|
||||
void mju_copy3(mjtNum res[3], const mjtNum data[3]);
|
||||
|
||||
@@ -120,16 +120,19 @@ Windows power plan so that the minimum processor state is 100%.
|
||||
`compile <https://github.com/google-deepmind/mujoco/blob/main/sample/compile.cc>`_
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This code sample evokes the built-in parser and compiler. It implements all possible model conversions from (MJCF, URDF,
|
||||
MJB) format to (MJCF, MJB, TXT) format. Models saved as MJCF use a canonical subset of our format as described in the
|
||||
:doc:`../modeling` chapter, and therefore MJCF-to-MJCF conversion will generally result in a different file.
|
||||
This code sample invokes the built-in parser and compiler. It implements all possible model conversions from (MJCF,
|
||||
URDF, MJB) format to (MJCF, MJB, TXT) format. Models saved as MJCF use a canonical subset of our format as described in
|
||||
the :doc:`../modeling` chapter, and therefore MJCF-to-MJCF conversion will generally result in a different file.
|
||||
The TXT format is a human-readable road-map to the model. It cannot be loaded by MuJoCo, but can be a very useful aid
|
||||
during model development. It is in one-to-one correspondence with the compiled mjModel. Note also that one can use the
|
||||
function :ref:`mj_printData` to create a text file which is in one-to-one correspondence
|
||||
with mjData, although this is not done by the code sample.
|
||||
|
||||
If the input file is MJCF and the output file is empty, compilation is performed and timed twice to measure the impact
|
||||
of the compiler's :ref:`asset cache<Assetcache>`.
|
||||
If the input file is MJCF or URDF and the output file is empty, compilation is performed twice to measure the impact
|
||||
of the compiler's :ref:`asset cache<Assetcache>`. A detailed timing breakdown is printed for each compilation, showing
|
||||
total time, asset processing time (wall clock), and per-category CPU times for meshes and textures. These timings are
|
||||
read from the :ref:`mjtCTimer` fields via :ref:`mjs_getTimer`, which can be read programmatically
|
||||
after any call to :ref:`mj_compile`.
|
||||
|
||||
.. _saBasic:
|
||||
|
||||
|
||||
@@ -129,6 +129,24 @@ typedef enum mjtOrientation_ { // type of orientation specifier
|
||||
} mjtOrientation;
|
||||
|
||||
|
||||
typedef enum mjtCTimer_ { // compiler timing categories
|
||||
// top-level timers (wall-clock)
|
||||
mjCTIMER_TOTAL = 0, // total compile time
|
||||
mjCTIMER_ASSETS, // asset compilation
|
||||
|
||||
// asset sub-timers (CPU time, summed across all assets)
|
||||
mjCTIMER_TEXTURE, // textures
|
||||
mjCTIMER_MESH_LOAD, // mesh: file loading
|
||||
mjCTIMER_MESH_HULL, // mesh: convex hull
|
||||
mjCTIMER_MESH_POLYGON, // mesh: normals and polygons
|
||||
mjCTIMER_MESH_INERTIA, // mesh: volume, CoM, inertia
|
||||
mjCTIMER_MESH_BVH, // mesh: bounding volume hierarchy
|
||||
mjCTIMER_MESH_OCTREE, // mesh: octree and SDF
|
||||
|
||||
mjNCTIMER // number of compiler timers
|
||||
} mjtCTimer;
|
||||
|
||||
|
||||
//-------------------------------- attribute structs (mjs) -----------------------------------------
|
||||
|
||||
typedef struct mjsElement_ { // element type, do not modify
|
||||
|
||||
@@ -1005,6 +1005,9 @@ MJAPI void mju_writeLog(const char* type, const char* msg);
|
||||
// Get compiler error message from spec.
|
||||
MJAPI const char* mjs_getError(mjSpec* s);
|
||||
|
||||
// Get compiler timing diagnostics from spec, returns pointer to array of size mjNCTIMER.
|
||||
MJAPI const double* mjs_getTimer(mjSpec* s);
|
||||
|
||||
// Return 1 if compiler error is a warning.
|
||||
MJAPI int mjs_isWarning(mjSpec* s);
|
||||
|
||||
|
||||
@@ -718,6 +718,23 @@ ENUMS: Mapping[str, EnumDecl] = dict([
|
||||
('mjORIENTATION_EULER', 4),
|
||||
]),
|
||||
)),
|
||||
('mjtCTimer',
|
||||
EnumDecl(
|
||||
name='mjtCTimer',
|
||||
declname='enum mjtCTimer_',
|
||||
values=dict([
|
||||
('mjCTIMER_TOTAL', 0),
|
||||
('mjCTIMER_ASSETS', 1),
|
||||
('mjCTIMER_TEXTURE', 2),
|
||||
('mjCTIMER_MESH_LOAD', 3),
|
||||
('mjCTIMER_MESH_HULL', 4),
|
||||
('mjCTIMER_MESH_POLYGON', 5),
|
||||
('mjCTIMER_MESH_INERTIA', 6),
|
||||
('mjCTIMER_MESH_BVH', 7),
|
||||
('mjCTIMER_MESH_OCTREE', 8),
|
||||
('mjNCTIMER', 9),
|
||||
]),
|
||||
)),
|
||||
('mjtCatBit',
|
||||
EnumDecl(
|
||||
name='mjtCatBit',
|
||||
|
||||
@@ -6471,6 +6471,22 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
|
||||
),
|
||||
doc='Get compiler error message from spec.',
|
||||
)),
|
||||
('mjs_getTimer',
|
||||
FunctionDecl(
|
||||
name='mjs_getTimer',
|
||||
return_type=PointerType(
|
||||
inner_type=ValueType(name='double', is_const=True),
|
||||
),
|
||||
parameters=(
|
||||
FunctionParameterDecl(
|
||||
name='s',
|
||||
type=PointerType(
|
||||
inner_type=ValueType(name='mjSpec'),
|
||||
),
|
||||
),
|
||||
),
|
||||
doc='Get compiler timing diagnostics from spec, returns pointer to array of size mjNCTIMER.', # pylint: disable=line-too-long
|
||||
)),
|
||||
('mjs_isWarning',
|
||||
FunctionDecl(
|
||||
name='mjs_isWarning',
|
||||
|
||||
@@ -55,6 +55,7 @@ using MjDouble3 = Eigen::Map<Eigen::Vector3d>;
|
||||
using MjDouble4 = Eigen::Map<Eigen::Vector4d>;
|
||||
using MjDouble5 = Eigen::Map<Eigen::Matrix<double, 5, 1>>;
|
||||
using MjDouble6 = Eigen::Map<Eigen::Matrix<double, 6, 1>>;
|
||||
using MjDouble9 = Eigen::Map<Eigen::Matrix<double, 9, 1>>;
|
||||
using MjDouble10 = Eigen::Map<Eigen::Matrix<double, 10, 1>>;
|
||||
using MjDouble11 = Eigen::Map<Eigen::Matrix<double, 11, 1>>;
|
||||
using MjDoubleVec = Eigen::Map<Eigen::VectorXd>;
|
||||
@@ -69,6 +70,7 @@ using MjDoubleRef3 = Eigen::Ref<const Eigen::Vector3d>;
|
||||
using MjDoubleRef4 = Eigen::Ref<const Eigen::Vector4d>;
|
||||
using MjDoubleRef5 = Eigen::Ref<const Eigen::Matrix<double, 5, 1>>;
|
||||
using MjDoubleRef6 = Eigen::Ref<const Eigen::Matrix<double, 6, 1>>;
|
||||
using MjDoubleRef9 = Eigen::Ref<const Eigen::Matrix<double, 9, 1>>;
|
||||
using MjDoubleRef10 = Eigen::Ref<const Eigen::Matrix<double, 10, 1>>;
|
||||
using MjDoubleRef11 = Eigen::Ref<const Eigen::Matrix<double, 11, 1>>;
|
||||
using MjDoubleRefVec = Eigen::Ref<const Eigen::VectorXd>;
|
||||
@@ -461,6 +463,12 @@ PYBIND11_MODULE(_specs, m) {
|
||||
mjSpec.def_property_readonly("_address", [](const MjSpec& self) {
|
||||
return reinterpret_cast<uintptr_t>(self.ptr);
|
||||
});
|
||||
mjSpec.def_property_readonly(
|
||||
"timer",
|
||||
[](MjSpec& self) -> MjDouble9 {
|
||||
return MjDouble9(const_cast<double*>(mjs_getTimer(self.ptr)));
|
||||
},
|
||||
py::return_value_policy::reference_internal);
|
||||
mjSpec.def_property(
|
||||
"copy_during_attach",
|
||||
[](MjSpec& self) {
|
||||
|
||||
@@ -47,6 +47,24 @@ class SpecsTest(absltest.TestCase):
|
||||
self.assertIsInstance(spec.worldbody, mujoco.MjsBody)
|
||||
self.assertIsInstance(spec.worldbody, typing.get_args(mujoco.MjStruct))
|
||||
|
||||
def test_timer(self):
|
||||
xml = """
|
||||
<mujoco>
|
||||
<asset>
|
||||
<texture name="grid" type="2d" builtin="checker" width="300" height="300" rgb1=".1 .2 .3" rgb2=".2 .3 .4"/>
|
||||
<material name="grid" texture="grid"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="plane" size="1 1 1" material="grid"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
"""
|
||||
spec = mujoco.MjSpec.from_string(xml)
|
||||
model = spec.compile()
|
||||
self.assertGreater(spec.timer[mujoco.mjtCTimer.mjCTIMER_TOTAL], 0)
|
||||
self.assertGreater(spec.timer[mujoco.mjtCTimer.mjCTIMER_ASSETS], 0)
|
||||
self.assertGreater(spec.timer[mujoco.mjtCTimer.mjCTIMER_TEXTURE], 0)
|
||||
|
||||
def test_basic(self):
|
||||
# Create a spec.
|
||||
spec = mujoco.MjSpec()
|
||||
|
||||
+45
-28
@@ -25,11 +25,11 @@
|
||||
|
||||
// help
|
||||
static constexpr char helpstring[] =
|
||||
"\n Usage: compile infile outfile\n"
|
||||
"\n Usage: compile infile [outfile]\n"
|
||||
" infile can be in mjcf, urdf, mjb format\n"
|
||||
" outfile can be in mjcf, mjb, txt format, or empty\n\n"
|
||||
" if infile is mjcf and outfile is empty, compilation will be "
|
||||
"timed twice to measure the impact of caching\n\n"
|
||||
" outfile can be in mjcf, mjb, txt format\n\n"
|
||||
" if infile is mjcf or urdf and outfile is omitted, a detailed\n"
|
||||
" timing breakdown is printed for two compilations (cold and warm cache)\n\n"
|
||||
" Example: compile model.xml [model.mjb]\n";
|
||||
|
||||
|
||||
@@ -137,17 +137,45 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
// print compiler timing diagnostics
|
||||
auto print_timers = [](const mjSpec* s, const char* label) {
|
||||
const double* timer = mjs_getTimer(const_cast<mjSpec*>(s));
|
||||
std::printf("\n%s:\n", label);
|
||||
std::printf(" total: %8.1f ms\n", 1e3 * timer[mjCTIMER_TOTAL]);
|
||||
std::printf(" assets: %8.1f ms (wall clock)\n", 1e3 * timer[mjCTIMER_ASSETS]);
|
||||
std::printf(" load: %8.1f ms\n", 1e3 * timer[mjCTIMER_MESH_LOAD]);
|
||||
std::printf(" hull: %8.1f ms\n", 1e3 * timer[mjCTIMER_MESH_HULL]);
|
||||
std::printf(" poly: %8.1f ms\n", 1e3 * timer[mjCTIMER_MESH_POLYGON]);
|
||||
std::printf(" inert: %8.1f ms\n", 1e3 * timer[mjCTIMER_MESH_INERTIA]);
|
||||
std::printf(" bvh: %8.1f ms\n", 1e3 * timer[mjCTIMER_MESH_BVH]);
|
||||
std::printf(" octr: %8.1f ms\n", 1e3 * timer[mjCTIMER_MESH_OCTREE]);
|
||||
std::printf(" tex: %8.1f ms\n", 1e3 * timer[mjCTIMER_TEXTURE]);
|
||||
std::printf(" other: %8.1f ms\n",
|
||||
1e3 * (timer[mjCTIMER_TOTAL] - timer[mjCTIMER_ASSETS]));
|
||||
};
|
||||
|
||||
// load model
|
||||
double first=0, second=0;
|
||||
mjSpec* s = nullptr;
|
||||
if (type1==typeXML) {
|
||||
double starttime = gettm();
|
||||
m = mj_loadXML(argv[1], 0, error, 1000);
|
||||
first = gettm() - starttime;
|
||||
if (m && type2 == typeNONE) {
|
||||
s = mj_parseXML(argv[1], 0, error, 1000);
|
||||
if (!s) {
|
||||
return finish(error, EXIT_FAILURE);
|
||||
}
|
||||
|
||||
m = mj_compile(s, 0);
|
||||
if (!m) {
|
||||
mj_deleteSpec(s);
|
||||
return finish("Could not compile model", EXIT_FAILURE);
|
||||
}
|
||||
|
||||
print_timers(s, "Compile 1 (cold cache)");
|
||||
|
||||
if (type2 == typeNONE) {
|
||||
mj_deleteModel(m);
|
||||
starttime = gettm();
|
||||
m = mj_loadXML(argv[1], 0, error, 1000);
|
||||
second = gettm() - starttime;
|
||||
m = mj_compile(s, 0);
|
||||
if (m) {
|
||||
print_timers(s, "Compile 2 (warm cache)");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m = mj_loadModel(argv[1], 0);
|
||||
@@ -155,16 +183,14 @@ int main(int argc, char** argv) {
|
||||
|
||||
// check error
|
||||
if (!m) {
|
||||
if (type1 == typeXML) {
|
||||
return finish(error, EXIT_FAILURE);
|
||||
} else {
|
||||
return finish("Could not load model", EXIT_FAILURE);
|
||||
}
|
||||
if (s) mj_deleteSpec(s);
|
||||
return finish("Could not load model", EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// save model
|
||||
if (type2 == typeXML) {
|
||||
if (!mj_saveLastXML(argv[2], m, error, 1000)) {
|
||||
if (s) mj_deleteSpec(s);
|
||||
return finish(error, EXIT_FAILURE, m);
|
||||
}
|
||||
} else if (type2 == typeMJB) {
|
||||
@@ -174,15 +200,6 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
// finalize
|
||||
char msg[1000];
|
||||
if (first && type2 == typeNONE) {
|
||||
snprintf(msg, sizeof(msg), "Done.\n"
|
||||
"First compile: %.4gs\n"
|
||||
"Second compile: %.4gs",
|
||||
first, second);
|
||||
} else {
|
||||
snprintf(msg, sizeof(msg), "Done.");
|
||||
}
|
||||
|
||||
return finish(msg, EXIT_SUCCESS, m);
|
||||
if (s) mj_deleteSpec(s);
|
||||
return finish("\nDone.", EXIT_SUCCESS, m);
|
||||
}
|
||||
|
||||
@@ -427,6 +427,17 @@ const char* mjs_getError(mjSpec* s) {
|
||||
|
||||
|
||||
|
||||
// get compiler timers from model
|
||||
const double* mjs_getTimer(mjSpec* s) {
|
||||
if (!s) {
|
||||
return nullptr;
|
||||
}
|
||||
mjCModel* modelC = static_cast<mjCModel*>(s->element);
|
||||
return modelC->timer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// check if model has warnings
|
||||
int mjs_isWarning(mjSpec* s) {
|
||||
mjCModel* modelC = static_cast<mjCModel*>(s->element);
|
||||
|
||||
+25
-4
@@ -14,19 +14,18 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <csetjmp>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
@@ -688,11 +687,17 @@ void mjCMesh::Compile(const mjVFS* vfs) {
|
||||
|
||||
// compiler
|
||||
void mjCMesh::TryCompile(const mjVFS* vfs) {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using Seconds = std::chrono::duration<double>;
|
||||
std::fill_n(mesh_timer_, mjNCTIMER, 0.0);
|
||||
|
||||
bool fromCache = false;
|
||||
CopyFromSpec();
|
||||
visual_ = true;
|
||||
mjCCache *cache = reinterpret_cast<mjCCache*>(mj_getCache()->impl_);
|
||||
|
||||
Clock::time_point t0 = Clock::now();
|
||||
|
||||
// load file
|
||||
if (!file_.empty()) {
|
||||
vert_.clear();
|
||||
@@ -765,12 +770,13 @@ void mjCMesh::TryCompile(const mjVFS* vfs) {
|
||||
LoadSDF(); // create using marching cubes
|
||||
}
|
||||
|
||||
mesh_timer_[mjCTIMER_MESH_LOAD] = Seconds(Clock::now() - t0).count();
|
||||
|
||||
CheckInitialMesh();
|
||||
|
||||
// compute mesh properties
|
||||
if (!fromCache) {
|
||||
Process();
|
||||
|
||||
if (!file_.empty()) {
|
||||
CacheMesh(cache, resource_);
|
||||
}
|
||||
@@ -778,6 +784,7 @@ void mjCMesh::TryCompile(const mjVFS* vfs) {
|
||||
// When a mesh is loaded from the cache, has no octree but needs one,
|
||||
// we need to compute it here. If inversely it has an octree but we *do not*
|
||||
// need one, we clear it.
|
||||
t0 = Clock::now();
|
||||
if (!needsdf) {
|
||||
octree_.Clear();
|
||||
} else if (octree_.NumNodes() == 0) {
|
||||
@@ -789,6 +796,7 @@ void mjCMesh::TryCompile(const mjVFS* vfs) {
|
||||
octree_.ComputeSdfCoeffs(dvert.data(), nvert(), face_.data(), nface(), tree_);
|
||||
}
|
||||
}
|
||||
mesh_timer_[mjCTIMER_MESH_OCTREE] = Seconds(Clock::now() - t0).count();
|
||||
}
|
||||
|
||||
// close resource
|
||||
@@ -1341,7 +1349,9 @@ double mjCMesh::ComputeFaceCentroid(double facecen[3], const double* dvert) cons
|
||||
|
||||
void mjCMesh::Process() {
|
||||
std::vector<double> dvert(vert_.begin(), vert_.end());
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using Seconds = std::chrono::duration<double>;
|
||||
Clock::time_point t0;
|
||||
// create half-edge structure (if mesh was in XML)
|
||||
if (halfedge_.empty()) {
|
||||
for (int i = 0; i < nface(); i++) {
|
||||
@@ -1370,6 +1380,7 @@ void mjCMesh::Process() {
|
||||
}
|
||||
}
|
||||
|
||||
t0 = Clock::now();
|
||||
// make graph describing convex hull
|
||||
if (needhull_ || face_.empty()) {
|
||||
MakeGraph(dvert.data());
|
||||
@@ -1379,7 +1390,9 @@ void mjCMesh::Process() {
|
||||
if (face_.empty()) {
|
||||
CopyGraph();
|
||||
}
|
||||
mesh_timer_[mjCTIMER_MESH_HULL] += Seconds(Clock::now() - t0).count();
|
||||
|
||||
t0 = Clock::now();
|
||||
// no normals: make
|
||||
if (normal_.empty()) {
|
||||
MakeNormal(dvert.data());
|
||||
@@ -1424,6 +1437,9 @@ void mjCMesh::Process() {
|
||||
}
|
||||
}
|
||||
|
||||
mesh_timer_[mjCTIMER_MESH_POLYGON] += Seconds(Clock::now() - t0).count();
|
||||
|
||||
t0 = Clock::now();
|
||||
// user offset, rotation, scaling
|
||||
ApplyTransformations(dvert.data());
|
||||
|
||||
@@ -1519,7 +1535,9 @@ void mjCMesh::Process() {
|
||||
|
||||
// recompute polygon normals
|
||||
MakePolygonNormals(dvert.data());
|
||||
mesh_timer_[mjCTIMER_MESH_INERTIA] += Seconds(Clock::now() - t0).count();
|
||||
|
||||
t0 = Clock::now();
|
||||
// make bounding volume hierarchy
|
||||
if (tree_.Bvh().empty()) {
|
||||
face_aabb_.clear();
|
||||
@@ -1530,7 +1548,9 @@ void mjCMesh::Process() {
|
||||
}
|
||||
tree_.CreateBVH();
|
||||
}
|
||||
mesh_timer_[mjCTIMER_MESH_BVH] += Seconds(Clock::now() - t0).count();
|
||||
|
||||
t0 = Clock::now();
|
||||
// make octree
|
||||
if (needsdf) {
|
||||
octree_.SetFace(dvert, face_);
|
||||
@@ -1546,6 +1566,7 @@ void mjCMesh::Process() {
|
||||
for (int i = 0; i < (int)dvert.size(); i++) {
|
||||
vert_[i] = (float)dvert[i];
|
||||
}
|
||||
mesh_timer_[mjCTIMER_MESH_OCTREE] += Seconds(Clock::now() - t0).count();
|
||||
}
|
||||
|
||||
|
||||
|
||||
+30
-6
@@ -16,7 +16,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <csetjmp>
|
||||
#include <cstdint>
|
||||
@@ -662,7 +662,6 @@ mjCModel& mjCModel::operator+=(mjCDef& subtree) {
|
||||
|
||||
// remove default class from array
|
||||
mjCModel& mjCModel::operator-=(const mjCDef& subtree) {
|
||||
|
||||
// check we aren't trying to remove the 'main' default
|
||||
if (subtree.id == 0) {
|
||||
throw mjCError(0, "cannot remove the global default ('main')");
|
||||
@@ -922,7 +921,7 @@ void mjCModel::ComputeSparseSizes() {
|
||||
|
||||
// 1. build dof_parentid, dof_bodyid
|
||||
if (nbody > 0) {
|
||||
body_lastdof_map[0] = -1; // world has no parent dof
|
||||
body_lastdof_map[0] = -1; // world has no parent dof
|
||||
}
|
||||
for (int i = 0; i < nbody; ++i) {
|
||||
mjCBody* pb = bodies_[i];
|
||||
@@ -961,7 +960,7 @@ void mjCModel::ComputeSparseSizes() {
|
||||
nD = 2 * nM - nv;
|
||||
|
||||
// 4. compute subtreedofs and nB
|
||||
for(int i = nbody - 1; i >= 0; --i) {
|
||||
for (int i = nbody - 1; i >= 0; --i) {
|
||||
bodies_[i]->subtreedofs = bodies_[i]->dofnum;
|
||||
for (const auto* child : bodies_[i]->Bodies()) {
|
||||
bodies_[i]->subtreedofs += child->subtreedofs;
|
||||
@@ -984,7 +983,7 @@ void mjCModel::ComputeSparseSizes() {
|
||||
}
|
||||
|
||||
// 5. compute nC
|
||||
for(int i = 0; i < nbody; ++i) {
|
||||
for (int i = 0; i < nbody; ++i) {
|
||||
mjCBody* pb = bodies_[i];
|
||||
mjCBody* par = pb->parent;
|
||||
int parentid = par ? par->id : 0;
|
||||
@@ -4744,10 +4743,13 @@ static void CompileMesh(mjCMesh* mesh, const mjVFS* vfs,
|
||||
static void CompileTexture(mjCTexture* texture, const mjVFS* vfs,
|
||||
std::exception_ptr& exception,
|
||||
std::mutex& exception_mutex, std::string* warningtext) {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using Seconds = std::chrono::duration<double>;
|
||||
local_warningtext_ptr = warningtext;
|
||||
auto previous_handler = _mjPRIVATE__get_tls_warning_fn();
|
||||
_mjPRIVATE__set_tls_warning_fn(warninghandler);
|
||||
|
||||
Clock::time_point t0 = Clock::now();
|
||||
try {
|
||||
texture->Compile(vfs);
|
||||
} catch (...) {
|
||||
@@ -4756,6 +4758,7 @@ static void CompileTexture(mjCTexture* texture, const mjVFS* vfs,
|
||||
exception = std::current_exception();
|
||||
}
|
||||
}
|
||||
texture->texture_time_ = Seconds(Clock::now() - t0).count();
|
||||
|
||||
_mjPRIVATE__set_tls_warning_fn(previous_handler);
|
||||
local_warningtext_ptr = nullptr;
|
||||
@@ -4843,6 +4846,15 @@ void mjCModel::CompileMeshesAndTextures(const mjVFS* vfs) {
|
||||
if (texture_exception) {
|
||||
std::rethrow_exception(texture_exception);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nmesh; i++) {
|
||||
for (int t = 0; t < mjNCTIMER; t++) {
|
||||
timer[t] += meshes_[i]->mesh_timer_[t];
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < ntexture; i++) {
|
||||
timer[mjCTIMER_TEXTURE] += textures_[i]->texture_time_;
|
||||
}
|
||||
}
|
||||
|
||||
// compute qpos0
|
||||
@@ -4984,6 +4996,12 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
||||
} disable_usethread(compiler.usethread);
|
||||
#endif
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using Seconds = std::chrono::duration<double>;
|
||||
for (int i=0; i < mjNCTIMER; i++) {
|
||||
timer[i] = 0;
|
||||
}
|
||||
Clock::time_point timer_start = Clock::now();
|
||||
// check if nan test works
|
||||
double test = mjNAN;
|
||||
if (mjuu_defined(test)) {
|
||||
@@ -5085,7 +5103,11 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
||||
SetNuser();
|
||||
|
||||
// compile meshes and textures (needed for geom compilation)
|
||||
CompileMeshesAndTextures(vfs);
|
||||
{
|
||||
Clock::time_point t0 = Clock::now();
|
||||
CompileMeshesAndTextures(vfs);
|
||||
timer[mjCTIMER_ASSETS] = Seconds(Clock::now() - t0).count();
|
||||
}
|
||||
|
||||
// compile objects in kinematic tree
|
||||
for (int i=0; i < bodies_.size(); i++) {
|
||||
@@ -5359,6 +5381,8 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
||||
// save signature
|
||||
m->signature = Signature();
|
||||
|
||||
timer[mjCTIMER_TOTAL] = Seconds(Clock::now() - timer_start).count();
|
||||
|
||||
// special cases that are not caused by user edits
|
||||
if (compiler.fusestatic || compiler.discardvisual ||
|
||||
!pairs_.empty() || !excludes_.empty()) {
|
||||
|
||||
@@ -198,6 +198,7 @@ class mjCModel : public mjCModel_, private mjSpec {
|
||||
mjCModel& operator-=(const mjCDef& subtree); // remove default tree from this model
|
||||
|
||||
mjSpec spec;
|
||||
double timer[mjNCTIMER] = {0}; // compiler timers
|
||||
|
||||
mjModel* Compile(const mjVFS* vfs = nullptr, mjModel** m = nullptr); // construct mjModel
|
||||
bool CopyBack(const mjModel*); // DECOMPILER: copy numeric back
|
||||
|
||||
@@ -1133,6 +1133,7 @@ class mjCMesh_ : public mjCBase {
|
||||
|
||||
// octree
|
||||
mjCOctree octree_; // octree of the mesh
|
||||
double mesh_timer_[mjNCTIMER] = {0};
|
||||
};
|
||||
|
||||
class mjCMesh: public mjCMesh_, private mjsMesh {
|
||||
@@ -1465,6 +1466,7 @@ class mjCTexture : public mjCTexture_, private mjsTexture {
|
||||
void PointToLocal(void);
|
||||
void NameSpace(const mjCModel* m);
|
||||
void Compile(const mjVFS* vfs);
|
||||
double texture_time_ = 0;
|
||||
|
||||
std::string File() const { return file_; }
|
||||
std::string get_content_type() const { return content_type_; }
|
||||
|
||||
@@ -3378,5 +3378,32 @@ TEST_F(MujocoTest, UserValue) {
|
||||
mj_deleteSpec(spec);
|
||||
}
|
||||
|
||||
TEST_F(MujocoTest, CompilerTimers) {
|
||||
static constexpr char xml[] = R"(
|
||||
<mujoco>
|
||||
<asset>
|
||||
<texture name="grid" type="2d" builtin="checker" width="300" height="300" rgb1=".1 .2 .3" rgb2=".2 .3 .4"/>
|
||||
<material name="grid" texture="grid"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="plane" size="1 1 1" material="grid"/>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
std::array<char, 1024> error;
|
||||
mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size());
|
||||
ASSERT_THAT(spec, NotNull()) << error.data();
|
||||
|
||||
mjModel* model = mj_compile(spec, 0);
|
||||
ASSERT_THAT(model, NotNull());
|
||||
|
||||
EXPECT_GT(mjs_getTimer(spec)[mjCTIMER_TOTAL], 0);
|
||||
EXPECT_GT(mjs_getTimer(spec)[mjCTIMER_ASSETS], 0);
|
||||
EXPECT_GT(mjs_getTimer(spec)[mjCTIMER_TEXTURE], 0);
|
||||
|
||||
mj_deleteModel(model);
|
||||
mj_deleteSpec(spec);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -573,6 +573,18 @@ public enum mjtOrientation : int{
|
||||
mjORIENTATION_ZAXIS = 3,
|
||||
mjORIENTATION_EULER = 4,
|
||||
}
|
||||
public enum mjtCTimer : int{
|
||||
mjCTIMER_TOTAL = 0,
|
||||
mjCTIMER_ASSETS = 1,
|
||||
mjCTIMER_TEXTURE = 2,
|
||||
mjCTIMER_MESH_LOAD = 3,
|
||||
mjCTIMER_MESH_HULL = 4,
|
||||
mjCTIMER_MESH_POLYGON = 5,
|
||||
mjCTIMER_MESH_INERTIA = 6,
|
||||
mjCTIMER_MESH_BVH = 7,
|
||||
mjCTIMER_MESH_OCTREE = 8,
|
||||
mjNCTIMER = 9,
|
||||
}
|
||||
public enum mjtCatBit : int{
|
||||
mjCAT_STATIC = 1,
|
||||
mjCAT_DYNAMIC = 2,
|
||||
|
||||
@@ -5612,6 +5612,7 @@ struct MjSpec {
|
||||
std::unique_ptr<MjSpec> copy();
|
||||
mjSpec* get() const;
|
||||
void set(mjSpec* ptr);
|
||||
emscripten::val timer() const;
|
||||
mjString modelname() const {
|
||||
return (ptr_ && ptr_->modelname) ? *(ptr_->modelname) : "";
|
||||
}
|
||||
@@ -8402,6 +8403,10 @@ MjSpec::~MjSpec() {
|
||||
mjSpec *MjSpec::get() const { return ptr_; }
|
||||
void MjSpec::set(mjSpec *ptr) { ptr_ = ptr; }
|
||||
|
||||
emscripten::val MjSpec::timer() const {
|
||||
return emscripten::val(emscripten::typed_memory_view(9, mjs_getTimer(ptr_)));
|
||||
}
|
||||
|
||||
std::unique_ptr<MjModel> mj_loadXML_wrapper_1(std::string filename) {
|
||||
char error[1000];
|
||||
mjModel *model = mj_loadXML(filename.c_str(), nullptr, error, sizeof(error));
|
||||
@@ -10911,6 +10916,17 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
.value("mjBUTTON_LEFT", mjBUTTON_LEFT)
|
||||
.value("mjBUTTON_RIGHT", mjBUTTON_RIGHT)
|
||||
.value("mjBUTTON_MIDDLE", mjBUTTON_MIDDLE);
|
||||
enum_<mjtCTimer>("mjtCTimer")
|
||||
.value("mjCTIMER_TOTAL", mjCTIMER_TOTAL)
|
||||
.value("mjCTIMER_ASSETS", mjCTIMER_ASSETS)
|
||||
.value("mjCTIMER_TEXTURE", mjCTIMER_TEXTURE)
|
||||
.value("mjCTIMER_MESH_LOAD", mjCTIMER_MESH_LOAD)
|
||||
.value("mjCTIMER_MESH_HULL", mjCTIMER_MESH_HULL)
|
||||
.value("mjCTIMER_MESH_POLYGON", mjCTIMER_MESH_POLYGON)
|
||||
.value("mjCTIMER_MESH_INERTIA", mjCTIMER_MESH_INERTIA)
|
||||
.value("mjCTIMER_MESH_BVH", mjCTIMER_MESH_BVH)
|
||||
.value("mjCTIMER_MESH_OCTREE", mjCTIMER_MESH_OCTREE)
|
||||
.value("mjNCTIMER", mjNCTIMER);
|
||||
enum_<mjtCamLight>("mjtCamLight")
|
||||
.value("mjCAMLIGHT_FIXED", mjCAMLIGHT_FIXED)
|
||||
.value("mjCAMLIGHT_TRACK", mjCAMLIGHT_TRACK)
|
||||
@@ -12371,6 +12387,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
||||
.property("nupdate", &MjSolverStat::nupdate, &MjSolverStat::set_nupdate, reference());
|
||||
emscripten::class_<MjSpec>("MjSpec")
|
||||
.constructor<const MjSpec &>()
|
||||
.property("timer", &MjSpec::timer)
|
||||
.property("comment", &MjSpec::comment, &MjSpec::set_comment, reference())
|
||||
.property("compiler", &MjSpec::compiler, reference())
|
||||
.property("element", &MjSpec::element, reference())
|
||||
|
||||
@@ -176,6 +176,7 @@ _SKIPPED_GETTERS_AND_SETTERS: tuple[str, ...] = (
|
||||
"mjs_getDouble",
|
||||
"mjs_getPluginAttributes",
|
||||
"mjs_getString",
|
||||
"mjs_getTimer",
|
||||
"mjs_getUserValue",
|
||||
"mjs_setBuffer",
|
||||
"mjs_setDouble",
|
||||
|
||||
@@ -381,6 +381,9 @@ def build_struct_header(
|
||||
builder.line(f"{s}* get() const;")
|
||||
builder.line(f"void set({s}* ptr);")
|
||||
|
||||
if w == "MjSpec":
|
||||
builder.line("emscripten::val timer() const;")
|
||||
|
||||
# field declarations
|
||||
for field in wrapped_fields:
|
||||
if field.declaration and field not in member_inits:
|
||||
@@ -598,6 +601,7 @@ def _build_struct_bindings(
|
||||
#undef X_ACCESSOR""".lstrip())
|
||||
elif w == "MjSpec":
|
||||
builder.line(".constructor<const MjSpec &>()")
|
||||
builder.line('.property("timer", &MjSpec::timer)')
|
||||
elif w == "MjvScene":
|
||||
builder.line(".constructor<MjModel *, int>()")
|
||||
builder.line(".constructor<>()")
|
||||
|
||||
@@ -715,6 +715,10 @@ MjSpec::~MjSpec() {
|
||||
mjSpec *MjSpec::get() const { return ptr_; }
|
||||
void MjSpec::set(mjSpec *ptr) { ptr_ = ptr; }
|
||||
|
||||
emscripten::val MjSpec::timer() const {
|
||||
return emscripten::val(emscripten::typed_memory_view(9, mjs_getTimer(ptr_)));
|
||||
}
|
||||
|
||||
std::unique_ptr<MjModel> mj_loadXML_wrapper_1(std::string filename) {
|
||||
char error[1000];
|
||||
mjModel *model = mj_loadXML(filename.c_str(), nullptr, error, sizeof(error));
|
||||
|
||||
Reference in New Issue
Block a user