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:
Yuval Tassa
2026-05-19 08:27:35 -07:00
committed by Copybara-Service
parent f712eed4ce
commit bdf00966f9
23 changed files with 309 additions and 43 deletions
+12
View File
@@ -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
+9
View File
@@ -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>`__
+4
View File
@@ -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
+17
View File
@@ -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]);
+8 -5
View File
@@ -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:
+18
View File
@@ -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
+3
View File
@@ -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);
+17
View File
@@ -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',
+16
View File
@@ -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',
+8
View File
@@ -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) {
+18
View File
@@ -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
View File
@@ -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);
}
+11
View File
@@ -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
View File
@@ -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
View File
@@ -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()) {
+1
View File
@@ -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
+2
View File
@@ -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_; }
+27
View File
@@ -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
+12
View File
@@ -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,
+17
View File
@@ -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())
+1
View File
@@ -176,6 +176,7 @@ _SKIPPED_GETTERS_AND_SETTERS: tuple[str, ...] = (
"mjs_getDouble",
"mjs_getPluginAttributes",
"mjs_getString",
"mjs_getTimer",
"mjs_getUserValue",
"mjs_setBuffer",
"mjs_setDouble",
+4
View File
@@ -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<>()")
+4
View File
@@ -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));