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
This commit is contained in:
Saran Tunyasuvunakool
2022-10-06 10:02:35 -07:00
committed by Copybara-Service
parent 4d85a464cc
commit 58fd72f53d
29 changed files with 1281 additions and 433 deletions
+2
View File
@@ -4188,6 +4188,8 @@ mj_rnePostConstraint
RNE with complete data: compute cacc, cfrc_ext, cfrc_int.
.. _mj_collision:
mj_collision
~~~~~~~~~~~~
+20 -24
View File
@@ -479,7 +479,7 @@ from its default.
This flag enables the simulation of sensor noise. When disabled (which is the default) noise is not added to
sensordata, even if the sensors specify non-zero noise amplitudes. When enabled, zero-mean Gaussian noise is added to
the underlying deterministic sensor data. Its standard deviation is determined by the noise parameter of each sensor.
:at:`multiccd`: :at-val:`[disable, enable], "disable"` **(experimental feature)**
:at:`multiccd`: :at-val:`[disable, enable], "disable"` |nbsp| |nbsp| |nbsp| (experimental feature)
This flag enables multiple-contact collision detection for geom pairs that use the general-purpose convex-convex
collider based on :ref:`libccd <coChecking>` e.g., mesh-mesh collisions. This can be useful when the contacting geoms
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
fields of mjOption which can be modified at runtime, sizes are structural parameters and should not be modified after
compilation.
:at:`njmax`: :at-val:`int, "-1"`
This and the next two attributes specify the maximum sizes of the dynamic arrays in mjData, i.e., arrays whose
effective length varies at runtime. This attribute specifies the maximum number of scalar constraints (or
equivalently, rows of the constraint Jacobian) that can be handled at runtime. If the number of active constraints is
about to exceed this maximum (usually because too many contacts become active) the extra constraints are discarded
and a warning is generated. The number of active constraints is stored in mjData.nefc. The default setting of -1
instructs the compiler to guess how much space to allocate (using heuristics that can be improved). This default is
effectively an undefined state. If the user specifies a positive value, the compiler heuristics are disabled and the
specified value is used. Modern computers have sufficient memory to handle very large models (larger than one would
normally have the patience to simulate) so tuning this setting aggressively is not necessary. When size-related
warnings or errors are generated, simply increase the value of the corresponding attribute.
:at:`nconmax`: :at-val:`int, "-1"`
This attribute specifies the maximum number of contacts (both frictional and frictionless) that can be handled at
runtime. If the number of active contacts is about to exceed this value, the extra contacts are discarded and a
warning is generated. The actual number of contacts is stored in mjData.ncon. If this value is negative, the compiler
will use a heuristic to guess an appropriate number.
:at:`nstack`: :at-val:`int, "-1"`
This attribute specifies the size of the preallocated stack in mjData, in units of sizeof(mjtNum) which is currently
defined as double; thus the size in bytes is 8 times larger. The custom stack is used by all MuJoCo functions that
need dynamically allocated memory. We do not use heap memory allocation at runtime, so as to speed up processing as
well as avoid heap fragmentation. Note that the internal allocator keeps track of how much stack space has ever been
utilized, in the field mjData.maxstackuse of mjData. If the stack size is exceeded at runtime, MuJoCo will generate
an error. If this value is negative, the compiler will use a heuristic to guess an appropriate number.
:at:`memory`: :at-val:`string, "-1"`
This attribute specifies the size of memory allocated for dynamic arrays in the ``mjData.arena`` memory space, in
bytes. The default setting of ``-1`` instructs the compiler to guess how much space to allocate. Appending the digits
with one of the letters {K, M, G, T, P, E} sets the unit to be {kilo, mega, giga, tera, peta, exa}-byte,
respectively. Thus "16M" means "allocate 16 megabytes of ``arena`` memory".
See the :ref:`Memory allocation <CSize>` section for details.
:at:`njmax`: :at-val:`int, "-1"` |nbsp| |nbsp| |nbsp| (legacy)
This is a deprecated legacy attribute. In versions prior to 2.3.0, it determined the maximum allowed number
of constraints. Currently it means "allocate as much memory as would have previously been required for this number of
constraints". Specifying both :at:`njmax` and :at:`memory` leads to an error.
:at:`nconmax`: :at-val:`int, "-1"` |nbsp| |nbsp| |nbsp| (legacy)
This attribute specifies the maximum number of contacts that will be generated at runtime. If the number of active
contacts is about to exceed this value, the extra contacts are discarded and a warning is generated. This is a
deprecated legacy attribute which prior to version 2.3.0 affected memory allocation. It is kept for backwards
compatibillity and debugging purposes.
:at:`nstack`: :at-val:`int, "-1"` |nbsp| |nbsp| |nbsp| (legacy)
This is a deprecated legacy attribute. In versions prior to 2.3.0, it determined the maximum size of the
:ref:`stack <siStack>`. Currently it is synonymous with the :at:`memory` attribute above, but is in units of
``sizeof(mjtNum)`` rather than bytes. Specifying both :at:`nstack` and :at:`memory` leads to an error.
:at:`nuserdata`: :at-val:`int, "0"`
The size of the field mjData.userdata of mjData. This field should be used to store custom dynamic variables. See
also :ref:`CUser`.
+14 -3
View File
@@ -9,9 +9,17 @@ Upcoming version (not yet released)
General
^^^^^^^
.. youtube:: RHnXD6uO3Mg
:align: right
:height: 150px
- The ``contact`` array and arrays prefixed with ``efc_`` in ``mjData`` were moved out of the ``buffer`` into a new
``arena`` memory space. These arrays are no longer allocated with fixed sizes when ``mjData`` is created.
Instead, the exact memory requirement is determined during each call to :ref:`mj_forward` (specifically,
in :ref:`mj_collision` and :ref:`mj_makeConstraint`) and the arrays are allocated from the ``arena`` space. The
``stack`` now also shares its available memory with ``arena``. This change reduces the memory footprint of ``mjData``
in models that do not use the PGS solver, and will allow for significant memory reductions in the future.
See the :ref:`Memory allocation <CSize>` section for details.
.. youtube:: RHnXD6uO3Mg
:align: right
:height: 150px
- Added colab notebook tutorial showing how to balance the humanoid on one leg with a Linear Quadratic Regulator. The
notebook uses MuJoCo's native Python bindings, and includes a draft ``Renderer`` class, for easy rendering in Python.
@@ -59,6 +67,9 @@ Python bindings
`named accessor <https://mujoco.readthedocs.io/en/latest/python.html#named-access>`_ objects. These provide more
Pythonic API access to ``mj_name2id`` and ``mj_id2name`` respectively.
- The length of ``MjData.contact`` is now ``ncon`` rather than ``nconmax``, allowing it to be straightforwardly used as
an iterator without needing to check ``ncon``.
Version 2.2.2 (September 7, 2022)
---------------------------------
+51 -26
View File
@@ -1276,6 +1276,57 @@ MuJoCo Forum for an example; the plots below are generated with that model.
|image18| |image19|
.. _CSize:
Memory allocation
~~~~~~~~~~~~~~~~~
MuJoCo preallocates all the memory needed at runtime in ``mjData``, and does not access the heap allocator after
model creation. Memory in ``mjData`` is allocated by :ref:`mj_makeData` in two contiguous blocks:
- ``mjData.buffer`` contains fixed-size arrays.
- ``mjData.arena`` contains dynamically-sized arrays.
There are two types of dynamic arrays allocated in the ``arena`` memory space.
- contacts and constraint-related arrays are laid out from the beginning of the ``arena``.
- :ref:`stack <siStack>` arrays are laid out from the end of the ``arena``.
By allocating dynamic quantities from both sides of the ``arena`` space, variable-sized memory allocation is controlled
by a single number: the :at:`memory` attribute of the :ref:`size <size>` MJCF element. Unlike the fixed-size arrays in
the ``buffer``, variable-sized arrays in the arena can be ``NULL``, for example after a call to :ref:`mj_resetData`.
When ``arena`` memory runs out, one of three things will happen, depending on the type of memory requested:
- If memory runs out during contact allocation, a warning will be raised and subsequent contacts will not be added in
this step, but simulation continues as usual.
- If memory runs out during constraint-related allocation, a warning will be raised and the constraint solver will be
disabled in this step, but simulation continues as usual. Note that physics without the constraint solver will
generally be very different, but allowing the simulation to continue can still be useful, e.g. during
scene initialization when many bodies are temporarily overlapping.
- If memory runs out during stack array allocation, a hard error will occur.
Unlike the size of the ``buffer``, the size of the ``arena`` cannot be pre-computed, since the number of contacts and
stack usage is not known in advance. So how should one choose it? The following simple heuristic is currently used,
though it may be improved in the future: enough memory is allocated for 100 contacts and 500 scalar constraints, under
worst-case conditions. If this heuristic is insufficient, we recommend the following procedure. Increase the ``arena``
memory significantly using the :at:`memory` attribute, and inspect the actual memory used at runtime.
``mjData.maxuse_arena`` keeps track of the maximum ``arena`` memory utilization since the last reset. The :ref:`simulate
<saSimulate>` viewer shows this number as a fraction of the total arena space (in the info window in the lower-left
corner). So one can start with a large number, simulate for a while, and if the fractions are small go back to the XML
and reduce the allocation size. Keep in mind though that memory utilization can change dramatically in the course of the
simulation, depending on how many constraints are active and which constraint solver is used. The CG solver is the most
memory efficient, followed by the Newton solver, while the PGS solver is the most memory intensive. When we design
models, we usually aim for 50% utilization in the worst-case scenario encountered while exploring the model. If you only
intend to use the CG solver, you can get away with significantly smaller arena allocation.
.. attention::
Memory allocation behaviour changed in MuJoCo 2.3.0. Before this version, the :at:`njmax`, :at:`nconmax` and
:at:`nstack` attributes of the :ref:`size <size>` MJCF element had the semantics of maximum memory allocated for
contacts, constraints and stack, respectively. If you are using an earlier version of MuJoCo, please switch to an
`earlier <https://mujoco.readthedocs.io/en/2.2.2/modeling.html#model-sizes>`_ documentation version to read about the
previous behaviour.
.. _Tips:
Tips and tricks
@@ -1377,32 +1428,6 @@ in a visible way, and the energy fluctuates around the initial value instead of
</body>
</worldbody>
.. _CSize:
Model sizes
~~~~~~~~~~~
MuJoCo preallocates all the memory needed at runtime in mjData, and does not access the C/C++ memory manager after
model creation. It is therefore essential to allocate enough memory. The allocation is controlled by three size
parameters specified in the :ref:`size <size>` element, namely the stack size :at:`nstack`, the
maximum number of contacts :at:`nconmax`, and the maximum number of scalar constraints :at:`njmax`. The default
size settings use heuristics to allocate sufficient memory, but the true memory needs for a given model can only be
determined during simulation. If nstack is insufficient the simulator calls mju_error and gives up. If nconmax or
njmax are insufficient the remaining contacts or other constraints are discarded, and the simulation continues but the
results are not as desired. If on the other hand the allocation is too large, clearing mjData with mj_reset takes
longer, and in multi-threaded applications simulating many large models in parallel the machine could run out of
memory, or cache performance could be adversely affected. And even if nothing bad happens, allocating a lot more
memory than needed is just poor style.
So how do we know how much memory to allocate? mjData has fields maxuse_stack, maxuse_con and maxuse_efc which keep
track of the maximum memory utilization in each category since the last reset. The code sample :ref:`simulate.cc <saSimulate>`
shows this data as a fraction of the maximum allocation (in the info window in the lower-left corner). So one can start with
the defaults, simulate for a while, and if the fractions are too small go back to the XML and set the allocation sizes
explicitly. Keep in mind though that memory utilization can change dramatically in the course of the simulation,
depending on how many constraints are active and also which constraint solver is used.
For example if the stack size is just sufficient for the CG solver, the Newton and PGS solvers will run out of stack.
When we design models, we usually aim for 50% utilization in the worst-case scenario encountered while exploring the
model. If you only intend to use the CG solver, you can get away with significantly smaller stack allocation.
.. |image0| image:: images/modeling/impedance.png
:width: 600px
+13 -20
View File
@@ -503,10 +503,10 @@ preallocated data arrays for all intermediate results, as well as an :ref:`inter
to allocate all necessary heap memory at the beginning of the simulation, and free it after the simulation is done, so
that we never have to call the C memory allocation and deallocation functions during the simulation. This is done for
speed, avoidance of memory fragmentation, future GPU portability, and ease of managing the state of the entire
simulator during a reset. It also means however that the maximal sizes :at:`njmax`, :at:`nconmax` and
:at:`nstack` in the XML element :ref:`size <size>`, which affect the allocation of mjData, must be
set to sufficiently large values. If these maximal sizes are exceeded during the simulation, they are not increased
dynamically, but instead errors or warnings are generated. See also :ref:`diagnostics <siDiagnostics>` below.
simulator during a reset. It also means however that the maximal variable-memory allocation given by the
:at:`memory` attribute in the :ref:`size <size>` MJCF element, which affects the allocation of ``mjData``, must be
set to a sufficiently large value. If this maximal size is exceeded during simulation, it is not increased
dynamically, but instead an error is generated. See also :ref:`diagnostics <siDiagnostics>` below.
First we must call one of the functions that allocates and initializes mjModel and returns a pointer to it. The
available options are
@@ -1308,22 +1308,15 @@ termination have similar order-of-magnitude as the numbers in ``mjData.fwdinv``,
different diagnostics.
Since MuJoCo's runtime works with compiled models, memory is preallocated when a model is compiled or loaded. Recall the
:ref:`size <size>` element in MJCF, which has the attributes :at:`njmax`, :at:`nconmax` and :at:`nstack`. They determine
the maximum number of scalar constraints that can be active simultaneously, the maximum number of contact points that
can be included in ``mjData.contact``, and the size of the internal stack. How is the user supposed to know what the
appropriate settings are? If there were a reliable recipe we would have implemented it in the compiler, but there isn't
one. The theoretical worst-case, namely all geoms contacting all other geoms, calls for huge allocation which is almost
never needed in practice. So our approach is to provide default settings in MJCF which are sufficient for most models,
and allow the user to adjust them manually with the above attributes. If the simulator runs out of stack space at
runtime it will trigger an error. If it runs out of space for contacts or scalar constraints, it will trigger a warning
and omit the contacts and constraints that do not fit in the allocated buffers. When such errors or warnings are
triggered, the user should adjust the sizes. The fields ``mjData.maxuse_stack``, ``mjData.maxuse_con``,
``mjData.maxuse_efc`` are designed to help with this adjustment. They keep track of the maximum stack allocation,
number of contacts and number of scalar constraints respectively since the last reset. So one strategy is to make very
large allocation, then monitor these ``maxuse_XXX`` statistics during typical simulations, and use them to reduce the
allocation. Of course modern computers have so much memory that most users will not bother with such adjustment once
they get rid of the out-of-memory errors and warnings, but nevertheless we provide this mechanism for the
perfectionist.
:at:`memory` attribute of the :ref:`size <size>` element in MJCF. It determines the preallocated space for dynamic
arrays. How is the user supposed to know what the appropriate value is? If there were a reliable recipe we would have
implemented it in the compiler, but there isn't one. The theoretical worst-case, namely all geoms contacting all other
geoms, calls for huge allocation which is almost never needed in practice. Our approach is to provide default settings
in MJCF which are sufficient for most models, and allow the user to adjust them manually with the above attribute. If
the simulator runs out of dynamic memory at runtime it will trigger an error. When such errors are triggered, the user
should increase :at:`memory`. The field ``mjData.maxuse_arena`` is designed to help with this adjustment. It keeps track
of the maximum arena use since the last reset. So one strategy is to make very large allocation, then monitor
``mjData.maxuse_memory`` statistics during typical simulations, and use it to reduce the allocation.
The kinetic and potential energy are computed and stored in ``mjData.energy`` when the corresponding flag in
``mjModel.opt.enableflags`` is set. This can be used as another diagnostic. In general, simulation instability is