Skip to content

Commit 58fd72f

Browse files
saran-tcopybara-github
authored andcommitted
Dynamically allocate contact and efc_ arrays on a new memory arena.
- Add private function `mj_arenaAlloc`. This is used internally to allocate memory from the arena. - Add private function `mj_nefc` to count constraints. This function returns a tight upper bound on `d->nefc`. The number of counted constraints can be slightly bigger than exact `d->nefc` in the case of constraints with empty Jacobian, as when placing a frictional tendon between two world sites. - Add new `memory` attribute to the `size` XML element for specification of arena memory size. This attribute is mutually exclusive with `nstack` and `njmax` specifications, which are now deprecated (but left around for the time being for legacy compatibility). - Move `d->stack` to the end of the new arena space. The stack now grows in reverse from the end. PiperOrigin-RevId: 479341539 Change-Id: Ie019c202e0908577ffc6f833a37920858116f667
1 parent 4d85a46 commit 58fd72f

29 files changed

+1281
-433
lines changed

doc/APIreference.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4188,6 +4188,8 @@ mj_rnePostConstraint
41884188
41894189
RNE with complete data: compute cacc, cfrc_ext, cfrc_int.
41904190

4191+
.. _mj_collision:
4192+
41914193
mj_collision
41924194
~~~~~~~~~~~~
41934195

doc/XMLreference.rst

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ from its default.
479479
This flag enables the simulation of sensor noise. When disabled (which is the default) noise is not added to
480480
sensordata, even if the sensors specify non-zero noise amplitudes. When enabled, zero-mean Gaussian noise is added to
481481
the underlying deterministic sensor data. Its standard deviation is determined by the noise parameter of each sensor.
482-
:at:`multiccd`: :at-val:`[disable, enable], "disable"` **(experimental feature)**
482+
:at:`multiccd`: :at-val:`[disable, enable], "disable"` |nbsp| |nbsp| |nbsp| (experimental feature)
483483
This flag enables multiple-contact collision detection for geom pairs that use the general-purpose convex-convex
484484
collider based on :ref:`libccd <coChecking>` e.g., mesh-mesh collisions. This can be useful when the contacting geoms
485485
have a flat surface, and the single contact point generated by the convex-convex collider cannot accurately capture
@@ -497,29 +497,25 @@ This element specifies size parameters that cannot be inferred from the number o
497497
fields of mjOption which can be modified at runtime, sizes are structural parameters and should not be modified after
498498
compilation.
499499

500-
:at:`njmax`: :at-val:`int, "-1"`
501-
This and the next two attributes specify the maximum sizes of the dynamic arrays in mjData, i.e., arrays whose
502-
effective length varies at runtime. This attribute specifies the maximum number of scalar constraints (or
503-
equivalently, rows of the constraint Jacobian) that can be handled at runtime. If the number of active constraints is
504-
about to exceed this maximum (usually because too many contacts become active) the extra constraints are discarded
505-
and a warning is generated. The number of active constraints is stored in mjData.nefc. The default setting of -1
506-
instructs the compiler to guess how much space to allocate (using heuristics that can be improved). This default is
507-
effectively an undefined state. If the user specifies a positive value, the compiler heuristics are disabled and the
508-
specified value is used. Modern computers have sufficient memory to handle very large models (larger than one would
509-
normally have the patience to simulate) so tuning this setting aggressively is not necessary. When size-related
510-
warnings or errors are generated, simply increase the value of the corresponding attribute.
511-
:at:`nconmax`: :at-val:`int, "-1"`
512-
This attribute specifies the maximum number of contacts (both frictional and frictionless) that can be handled at
513-
runtime. If the number of active contacts is about to exceed this value, the extra contacts are discarded and a
514-
warning is generated. The actual number of contacts is stored in mjData.ncon. If this value is negative, the compiler
515-
will use a heuristic to guess an appropriate number.
516-
:at:`nstack`: :at-val:`int, "-1"`
517-
This attribute specifies the size of the preallocated stack in mjData, in units of sizeof(mjtNum) which is currently
518-
defined as double; thus the size in bytes is 8 times larger. The custom stack is used by all MuJoCo functions that
519-
need dynamically allocated memory. We do not use heap memory allocation at runtime, so as to speed up processing as
520-
well as avoid heap fragmentation. Note that the internal allocator keeps track of how much stack space has ever been
521-
utilized, in the field mjData.maxstackuse of mjData. If the stack size is exceeded at runtime, MuJoCo will generate
522-
an error. If this value is negative, the compiler will use a heuristic to guess an appropriate number.
500+
:at:`memory`: :at-val:`string, "-1"`
501+
This attribute specifies the size of memory allocated for dynamic arrays in the ``mjData.arena`` memory space, in
502+
bytes. The default setting of ``-1`` instructs the compiler to guess how much space to allocate. Appending the digits
503+
with one of the letters {K, M, G, T, P, E} sets the unit to be {kilo, mega, giga, tera, peta, exa}-byte,
504+
respectively. Thus "16M" means "allocate 16 megabytes of ``arena`` memory".
505+
See the :ref:`Memory allocation <CSize>` section for details.
506+
:at:`njmax`: :at-val:`int, "-1"` |nbsp| |nbsp| |nbsp| (legacy)
507+
This is a deprecated legacy attribute. In versions prior to 2.3.0, it determined the maximum allowed number
508+
of constraints. Currently it means "allocate as much memory as would have previously been required for this number of
509+
constraints". Specifying both :at:`njmax` and :at:`memory` leads to an error.
510+
:at:`nconmax`: :at-val:`int, "-1"` |nbsp| |nbsp| |nbsp| (legacy)
511+
This attribute specifies the maximum number of contacts that will be generated at runtime. If the number of active
512+
contacts is about to exceed this value, the extra contacts are discarded and a warning is generated. This is a
513+
deprecated legacy attribute which prior to version 2.3.0 affected memory allocation. It is kept for backwards
514+
compatibillity and debugging purposes.
515+
:at:`nstack`: :at-val:`int, "-1"` |nbsp| |nbsp| |nbsp| (legacy)
516+
This is a deprecated legacy attribute. In versions prior to 2.3.0, it determined the maximum size of the
517+
:ref:`stack <siStack>`. Currently it is synonymous with the :at:`memory` attribute above, but is in units of
518+
``sizeof(mjtNum)`` rather than bytes. Specifying both :at:`nstack` and :at:`memory` leads to an error.
523519
:at:`nuserdata`: :at-val:`int, "0"`
524520
The size of the field mjData.userdata of mjData. This field should be used to store custom dynamic variables. See
525521
also :ref:`CUser`.

doc/changelog.rst

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@ Upcoming version (not yet released)
99
General
1010
^^^^^^^
1111

12-
.. youtube:: RHnXD6uO3Mg
13-
:align: right
14-
:height: 150px
12+
- The ``contact`` array and arrays prefixed with ``efc_`` in ``mjData`` were moved out of the ``buffer`` into a new
13+
``arena`` memory space. These arrays are no longer allocated with fixed sizes when ``mjData`` is created.
14+
Instead, the exact memory requirement is determined during each call to :ref:`mj_forward` (specifically,
15+
in :ref:`mj_collision` and :ref:`mj_makeConstraint`) and the arrays are allocated from the ``arena`` space. The
16+
``stack`` now also shares its available memory with ``arena``. This change reduces the memory footprint of ``mjData``
17+
in models that do not use the PGS solver, and will allow for significant memory reductions in the future.
18+
See the :ref:`Memory allocation <CSize>` section for details.
19+
20+
.. youtube:: RHnXD6uO3Mg
21+
:align: right
22+
:height: 150px
1523

1624
- Added colab notebook tutorial showing how to balance the humanoid on one leg with a Linear Quadratic Regulator. The
1725
notebook uses MuJoCo's native Python bindings, and includes a draft ``Renderer`` class, for easy rendering in Python.
@@ -59,6 +67,9 @@ Python bindings
5967
`named accessor <https://mujoco.readthedocs.io/en/latest/python.html#named-access>`_ objects. These provide more
6068
Pythonic API access to ``mj_name2id`` and ``mj_id2name`` respectively.
6169

70+
- The length of ``MjData.contact`` is now ``ncon`` rather than ``nconmax``, allowing it to be straightforwardly used as
71+
an iterator without needing to check ``ncon``.
72+
6273

6374
Version 2.2.2 (September 7, 2022)
6475
---------------------------------

doc/modeling.rst

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,57 @@ MuJoCo Forum for an example; the plots below are generated with that model.
12761276

12771277
|image18| |image19|
12781278

1279+
.. _CSize:
1280+
1281+
Memory allocation
1282+
~~~~~~~~~~~~~~~~~
1283+
1284+
MuJoCo preallocates all the memory needed at runtime in ``mjData``, and does not access the heap allocator after
1285+
model creation. Memory in ``mjData`` is allocated by :ref:`mj_makeData` in two contiguous blocks:
1286+
1287+
- ``mjData.buffer`` contains fixed-size arrays.
1288+
- ``mjData.arena`` contains dynamically-sized arrays.
1289+
1290+
There are two types of dynamic arrays allocated in the ``arena`` memory space.
1291+
1292+
- contacts and constraint-related arrays are laid out from the beginning of the ``arena``.
1293+
- :ref:`stack <siStack>` arrays are laid out from the end of the ``arena``.
1294+
1295+
By allocating dynamic quantities from both sides of the ``arena`` space, variable-sized memory allocation is controlled
1296+
by a single number: the :at:`memory` attribute of the :ref:`size <size>` MJCF element. Unlike the fixed-size arrays in
1297+
the ``buffer``, variable-sized arrays in the arena can be ``NULL``, for example after a call to :ref:`mj_resetData`.
1298+
When ``arena`` memory runs out, one of three things will happen, depending on the type of memory requested:
1299+
1300+
- If memory runs out during contact allocation, a warning will be raised and subsequent contacts will not be added in
1301+
this step, but simulation continues as usual.
1302+
- If memory runs out during constraint-related allocation, a warning will be raised and the constraint solver will be
1303+
disabled in this step, but simulation continues as usual. Note that physics without the constraint solver will
1304+
generally be very different, but allowing the simulation to continue can still be useful, e.g. during
1305+
scene initialization when many bodies are temporarily overlapping.
1306+
- If memory runs out during stack array allocation, a hard error will occur.
1307+
1308+
Unlike the size of the ``buffer``, the size of the ``arena`` cannot be pre-computed, since the number of contacts and
1309+
stack usage is not known in advance. So how should one choose it? The following simple heuristic is currently used,
1310+
though it may be improved in the future: enough memory is allocated for 100 contacts and 500 scalar constraints, under
1311+
worst-case conditions. If this heuristic is insufficient, we recommend the following procedure. Increase the ``arena``
1312+
memory significantly using the :at:`memory` attribute, and inspect the actual memory used at runtime.
1313+
``mjData.maxuse_arena`` keeps track of the maximum ``arena`` memory utilization since the last reset. The :ref:`simulate
1314+
<saSimulate>` viewer shows this number as a fraction of the total arena space (in the info window in the lower-left
1315+
corner). So one can start with a large number, simulate for a while, and if the fractions are small go back to the XML
1316+
and reduce the allocation size. Keep in mind though that memory utilization can change dramatically in the course of the
1317+
simulation, depending on how many constraints are active and which constraint solver is used. The CG solver is the most
1318+
memory efficient, followed by the Newton solver, while the PGS solver is the most memory intensive. When we design
1319+
models, we usually aim for 50% utilization in the worst-case scenario encountered while exploring the model. If you only
1320+
intend to use the CG solver, you can get away with significantly smaller arena allocation.
1321+
1322+
.. attention::
1323+
1324+
Memory allocation behaviour changed in MuJoCo 2.3.0. Before this version, the :at:`njmax`, :at:`nconmax` and
1325+
:at:`nstack` attributes of the :ref:`size <size>` MJCF element had the semantics of maximum memory allocated for
1326+
contacts, constraints and stack, respectively. If you are using an earlier version of MuJoCo, please switch to an
1327+
`earlier <https://mujoco.readthedocs.io/en/2.2.2/modeling.html#model-sizes>`_ documentation version to read about the
1328+
previous behaviour.
1329+
12791330
.. _Tips:
12801331

12811332
Tips and tricks
@@ -1377,32 +1428,6 @@ in a visible way, and the energy fluctuates around the initial value instead of
13771428
</body>
13781429
</worldbody>
13791430
1380-
.. _CSize:
1381-
1382-
Model sizes
1383-
~~~~~~~~~~~
1384-
1385-
MuJoCo preallocates all the memory needed at runtime in mjData, and does not access the C/C++ memory manager after
1386-
model creation. It is therefore essential to allocate enough memory. The allocation is controlled by three size
1387-
parameters specified in the :ref:`size <size>` element, namely the stack size :at:`nstack`, the
1388-
maximum number of contacts :at:`nconmax`, and the maximum number of scalar constraints :at:`njmax`. The default
1389-
size settings use heuristics to allocate sufficient memory, but the true memory needs for a given model can only be
1390-
determined during simulation. If nstack is insufficient the simulator calls mju_error and gives up. If nconmax or
1391-
njmax are insufficient the remaining contacts or other constraints are discarded, and the simulation continues but the
1392-
results are not as desired. If on the other hand the allocation is too large, clearing mjData with mj_reset takes
1393-
longer, and in multi-threaded applications simulating many large models in parallel the machine could run out of
1394-
memory, or cache performance could be adversely affected. And even if nothing bad happens, allocating a lot more
1395-
memory than needed is just poor style.
1396-
1397-
So how do we know how much memory to allocate? mjData has fields maxuse_stack, maxuse_con and maxuse_efc which keep
1398-
track of the maximum memory utilization in each category since the last reset. The code sample :ref:`simulate.cc <saSimulate>`
1399-
shows this data as a fraction of the maximum allocation (in the info window in the lower-left corner). So one can start with
1400-
the defaults, simulate for a while, and if the fractions are too small go back to the XML and set the allocation sizes
1401-
explicitly. Keep in mind though that memory utilization can change dramatically in the course of the simulation,
1402-
depending on how many constraints are active and also which constraint solver is used.
1403-
For example if the stack size is just sufficient for the CG solver, the Newton and PGS solvers will run out of stack.
1404-
When we design models, we usually aim for 50% utilization in the worst-case scenario encountered while exploring the
1405-
model. If you only intend to use the CG solver, you can get away with significantly smaller stack allocation.
14061431
14071432
.. |image0| image:: images/modeling/impedance.png
14081433
:width: 600px

doc/programming.rst

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -503,10 +503,10 @@ preallocated data arrays for all intermediate results, as well as an :ref:`inter
503503
to allocate all necessary heap memory at the beginning of the simulation, and free it after the simulation is done, so
504504
that we never have to call the C memory allocation and deallocation functions during the simulation. This is done for
505505
speed, avoidance of memory fragmentation, future GPU portability, and ease of managing the state of the entire
506-
simulator during a reset. It also means however that the maximal sizes :at:`njmax`, :at:`nconmax` and
507-
:at:`nstack` in the XML element :ref:`size <size>`, which affect the allocation of mjData, must be
508-
set to sufficiently large values. If these maximal sizes are exceeded during the simulation, they are not increased
509-
dynamically, but instead errors or warnings are generated. See also :ref:`diagnostics <siDiagnostics>` below.
506+
simulator during a reset. It also means however that the maximal variable-memory allocation given by the
507+
:at:`memory` attribute in the :ref:`size <size>` MJCF element, which affects the allocation of ``mjData``, must be
508+
set to a sufficiently large value. If this maximal size is exceeded during simulation, it is not increased
509+
dynamically, but instead an error is generated. See also :ref:`diagnostics <siDiagnostics>` below.
510510

511511
First we must call one of the functions that allocates and initializes mjModel and returns a pointer to it. The
512512
available options are
@@ -1308,22 +1308,15 @@ termination have similar order-of-magnitude as the numbers in ``mjData.fwdinv``,
13081308
different diagnostics.
13091309

13101310
Since MuJoCo's runtime works with compiled models, memory is preallocated when a model is compiled or loaded. Recall the
1311-
:ref:`size <size>` element in MJCF, which has the attributes :at:`njmax`, :at:`nconmax` and :at:`nstack`. They determine
1312-
the maximum number of scalar constraints that can be active simultaneously, the maximum number of contact points that
1313-
can be included in ``mjData.contact``, and the size of the internal stack. How is the user supposed to know what the
1314-
appropriate settings are? If there were a reliable recipe we would have implemented it in the compiler, but there isn't
1315-
one. The theoretical worst-case, namely all geoms contacting all other geoms, calls for huge allocation which is almost
1316-
never needed in practice. So our approach is to provide default settings in MJCF which are sufficient for most models,
1317-
and allow the user to adjust them manually with the above attributes. If the simulator runs out of stack space at
1318-
runtime it will trigger an error. If it runs out of space for contacts or scalar constraints, it will trigger a warning
1319-
and omit the contacts and constraints that do not fit in the allocated buffers. When such errors or warnings are
1320-
triggered, the user should adjust the sizes. The fields ``mjData.maxuse_stack``, ``mjData.maxuse_con``,
1321-
``mjData.maxuse_efc`` are designed to help with this adjustment. They keep track of the maximum stack allocation,
1322-
number of contacts and number of scalar constraints respectively since the last reset. So one strategy is to make very
1323-
large allocation, then monitor these ``maxuse_XXX`` statistics during typical simulations, and use them to reduce the
1324-
allocation. Of course modern computers have so much memory that most users will not bother with such adjustment once
1325-
they get rid of the out-of-memory errors and warnings, but nevertheless we provide this mechanism for the
1326-
perfectionist.
1311+
:at:`memory` attribute of the :ref:`size <size>` element in MJCF. It determines the preallocated space for dynamic
1312+
arrays. How is the user supposed to know what the appropriate value is? If there were a reliable recipe we would have
1313+
implemented it in the compiler, but there isn't one. The theoretical worst-case, namely all geoms contacting all other
1314+
geoms, calls for huge allocation which is almost never needed in practice. Our approach is to provide default settings
1315+
in MJCF which are sufficient for most models, and allow the user to adjust them manually with the above attribute. If
1316+
the simulator runs out of dynamic memory at runtime it will trigger an error. When such errors are triggered, the user
1317+
should increase :at:`memory`. The field ``mjData.maxuse_arena`` is designed to help with this adjustment. It keeps track
1318+
of the maximum arena use since the last reset. So one strategy is to make very large allocation, then monitor
1319+
``mjData.maxuse_memory`` statistics during typical simulations, and use it to reduce the allocation.
13271320

13281321
The kinetic and potential energy are computed and stored in ``mjData.energy`` when the corresponding flag in
13291322
``mjModel.opt.enableflags`` is set. This can be used as another diagnostic. In general, simulation instability is

0 commit comments

Comments
 (0)