2. COCOS2D-X Memory Management Mechanism

Source: Internet
Author: User

2. COCOS2D-X Memory Management Mechanism
In C ++, dynamic memory allocation is a double-edged sword. On the one hand, direct access to memory addresses improves the performance of applications and the flexibility of using memory. On the other hand, problems such as wild pointer, repeated release, and memory leakage caused by incorrect program allocation and release seriously affect application stability.


People try different solutions to avoid this problem, such as smart pointers and automatic garbage collection, which may affect the performance of applications, either you still need to rely on the developer to pay attention to some rules, or bring some ugly usage to the developer (in fact, the author does not like smart pointers ). Therefore, the excellent C ++ memory management solution requires both performance and ease of use. So far, the C ++ standard has not provided a real memory management solution.


The memory management mechanism of the Cocos2d-x is actually derived from Objective-C, which almost runs through all the dynamic allocation objects in the Cocos2d-x. It makes it easier to manage objects dynamically distributed to the stack. However, its unique working mechanism also makes some developers, especially those not familiar with Objective-C, "misunderstand ". Ensuring a complete understanding and correct use of the memory management mechanism of the Cocos2d-x is the basic preparation required to use the Cocos2d-x.


3.2.1 C ++ explicit heap memory management


C ++ uses the new Keyword to dynamically allocate memory to an object at runtime, and returns the address of the memory on the heap for application access, the dynamically allocated memory needs to return its memory to the memory pool through the delete operator when the object is no longer in use.


Explicit Memory Management has some advantages in performance, but is extremely prone to errors. In fact, we cannot always ensure a logic is correct through human thinking. Failure to properly handle heap memory allocation and release usually results in the following problems:
Swap wild pointer: The Memory Unit pointed to by the pointer has been released, but some other pointers may also point to it. These memories may have been reassigned to other objects, leading to unpredictable results.
Repeated release of memory: repeated release of a released memory unit, or release of a wild pointer (also released repeatedly) will cause a C ++ runtime error.
Memory leakage in SWAp: memory units that are no longer in use will continue to occupy memory units if they are not released. If these operations are repeated, the memory usage will continue to increase, memory leakage is especially serious in the game, because each frame may be creating a game object that will never be recycled.


3.2.2 smart pointer in C ++ 11


Based on the memory allocation method, C ++ has three data memory management methods: Automatic Storage, static storage, and dynamic storage. Static storage is used to store some static variables that exist throughout the application execution, and dynamic storage is used to store the memory units allocated through new as described in the previous section.


For general variables defined in the function, automatic storage space is used. The corresponding variables are called automatic variables. The automatic variable is automatically generated when the function to which it belongs is called and disappears when the function ends. In fact, an automatic variable is a local variable whose scope is the code block containing it. Automatic variables are usually stored on the stack, which means that when you enter the code block, the variables in the code block will be added to the stack in sequence, and the variables will be released in reverse order when you exit the code block.


Because automatic variables usually do not cause memory problems, smart pointers attempt to associate a dynamically allocated memory unit with an automatic variable, this automatic variable releases its memory unit when it leaves the code block and is automatically released, so that the programmer no longer needs to explicitly call delete to manage the dynamically allocated memory.


C ++ 11 uses three different smart pointers: unique_ptr, shared_ptr, and weak_ptr. They are all template types. We can use them as follows:


Int main (){
Unique_ptr up1 (new int (11 ));
Unique_ptr up11 = up1; // compilation Error


Shared_ptr up2 (new int (22 ));
Weak_ptr up3 = up2;
}


Every smart pointer is overloaded with the * operator. We can use * up1 to access the allocated heap memory. When you describe or call a reset member, the smart pointer may release its heap memory. The differences between the three are as follows:
 Unique_ptr cannot share the memory of the specified object with other smart pointers. For example, assigning up1 to up11 will cause compilation errors. However, you can use the move function of the standard library to transfer the "ownership" of the unique_ptr object. Once the transfer is successful, the original unique_ptr pointer will lose the ownership of the object memory, if you use it again, it will cause a running error.
Multiple shared_ptr objects in the shard can share the memory of the same heap allocation object. It uses reference counting in implementation. Once a shared_ptr abandons ownership (the reset member is called) other smart pointer objects are not affected. Only when the reference count is set to zero will the occupied heap memory space be truly released.
 Weak_ptr can be used to point to the object memory allocated by shared_ptr, but it does not own the memory. We can use its lock member to access a shared_ptr object pointing to the memory, when the memory it points to is invalid, the NULL pointer (nullptr) is returned ). Weak_ptr can be used to verify the validity of shared_ptr.


3.2.3 why not use smart pointers


It looks like shared_ptr is a perfect memory management solution, but there are actually at least two reasons why the Cocos2d-x should not use smart pointers:


First of all, the smart pointer has a relatively large performance loss, the Cocos2d-x forum has discussed whether to use the smart pointer post [Reference 1], shared_ptr in order to ensure thread security, a certain form of mutex must be used to ensure that the reference count of all threads is correct during access. This kind of performance loss is no problem for general use. However, it is unacceptable for games that have very high real-time performance. Games require a simpler memory management model.


Secondly, although the smart pointer can help the programmer to effectively manage heap memory, it still requires the programmer to explicitly declare the smart pointer. For example, the Code for creating a Node needs to be written as follows:


Shared_ptr node (new Node ());


In addition, weak_ptr should be used where we need to reference it. Otherwise, shared_ptr will point to a released memory when the Node is removed, resulting in a runtime error:


Weak_ptr refNode = node;


These extra constraints make smart pointers very difficult to use. Therefore, I hate smart pointers very much. This method can be used to avoid logical errors, but it is not an elegant method. After all, our programmers need to face the code every day, and we need a more natural memory management method, just like the features of the language itself, we can hardly even notice the mechanisms behind it.


3.2.4 garbage collection mechanism


In fact, such a solution already exists, which is the garbage collection mechanism. The heap memory management of garbage collection is called "garbage" by memory space that has been used before and is no longer used or has no pointer to it ", the mechanism that collects these "spam" for reuse is called "Garbage Collection ". Garbage collection was invented by John macary for The Lisp Language around 1959. During the development of programming languages, the heap memory management of garbage collection has also been greatly developed. Currently, some popular languages such as Java, C #, Ruby, PHP, and Perl support the garbage collection mechanism.


There are two main methods for garbage collection:
Reference counting: The reference counting system records the number of times an object is referenced. When the number of times an object is referenced becomes 0, the object is recycled as garbage. The advantage of this algorithm is that the implementation method is relatively simple.
Tracing processing: This method generates the relationship diagram of the tracing object and then recycles the object. The algorithm first regards the objects being used in the program as "root objects", searches for the heap space they reference from the root object, and marks the heap space. When the Tag ends, all unlabeled objects are treated as spam and cleared in the second stage. In the second stage, different methods can be used for cleanup. Direct cleanup may produce a large amount of garbage fragments, and other methods can be used to move or copy the objects in use, thus reducing the generation of memory fragments.


In either way, automatic garbage collection can make memory management more natural, and more importantly, programmers almost do not have to do anything constrained.


3.2.5 Cocos2d-x Memory Management Mechanism


However, the garbage collection mechanism usually requires language implementation. C ++ does not currently include a complete garbage collection mechanism. The memory management mechanism in Cocos2d-x is actually a variant based on smart pointers. But it also allows programmers to do not need to declare smart pointers like the garbage collection mechanism.


3.2.5.1 reference count


Almost all objects in the Cocos2d-x inherit from the Ref base class, and the unique role of Ref is to manage the reference count of objects:


Class CC_DLL Ref
{
Public:
Void retain ();
Void release ();
Ref * autorelease ();
Unsigned int getReferenceCount () const;


Protected:
Ref ();


Protected:
/// Count of references
Unsigned int _ referenceCount;
Friend class AutoreleasePool;
};


When an object is allocated memory using the new operator, the reference count is 1. Calling the retain () method increases the reference count, and calling the release () method reduces the reference count, the release () method will automatically call the delete operator when its reference count is 0 to delete objects and release the memory.


Besides, retain and release do not do anything special. It only helps us record the number of times an object is referenced. In fact, retain and release are rarely used independently in programs, in the end, the most important thing is to determine where it should be released during design. Most references are just weak references, using retain and release increases complexity.
Let's take a look at how we manage the UI elements when there is only reference count:


Auto node = new Node (); // The reference count is 1.
AddChild (node); // The reference count is 2.
......


Node-> removeFromParent (); // The reference count is 1.
Node-> release (); // The reference count is 0, and the object is deleted.


We immediately discovered that this is not the expected result. If we forget to call release, memory leakage will occur.


3.2.5.2 autorel declare a pointer as a "smart pointer"


Recall the smart pointer mentioned above. If a dynamically allocated memory is associated with an automatic variable, the heap memory will be released when the lifecycle of the automatic variable ends, so that programmers do not have to worry about the release of their memory. Can we use a similar mechanism to avoid manually releasing UI elements?


The Cocos2d-x uses autorelease to declare an object pointer as a "smart pointer", but these "smart Pointers" are not independently associated with an automatic variable, but are all added to an AutoreleasePool. At the end of each frame, objects added to the AutoreleasePool are cleared, that is, in the Cocos2d-x, the lifecycle of a smart pointer is from creation to the end of the current frame.


Ref * Ref: autorelease ()
{
PoolManager: getInstance ()-> getCurrentPool ()-> addObject (this );
Return this;
}


In the code above, the Cocos2d-x adds an object to the AutoreleasePool through the autorelease method.


Void DisplayLinkDirector: mainLoop ()
{
If (! _ Invalid)
{
DrawScene ();


// Release the objects
PoolManager: getInstance ()-> getCurrentPool ()-> clear ();
}
}


In the code above, the Cocos2d-x clears the objects in the AutoreleasePool at the end of each frame.


Void AutoreleasePool: clear ()
{
# If defined (COCOS2D_DEBUG) & (COCOS2D_DEBUG> 0)
_ IsClearing = true;
# Endif
For (const auto & obj: _ managedObjectArray)
{
Obj-> release ();
}
_ ManagedObjectArray. clear ();
# If defined (COCOS2D_DEBUG) & (COCOS2D_DEBUG> 0)
_ IsClearing = false;
# Endif
}


The actual implementation mechanism is that AutoreleasePool performs a release operation on each object in the pool. If the reference count of this object is 1, it indicates that the object has never been used, the reference count is 0 after the release is executed, will be released. For example, create an unused Node:


Auto node = new Node (); // The reference count is 1.
Node-> autorelease (); // Add to "smart pointer pool"


It is expected that the node object will be automatically released at the end of the frame. If this object is used:


Auto node = new Node (); // The reference count is 1.
Node-> autorelease (); // Add to "smart pointer pool"
AddChild (node); // The reference count is 2.


At the end of the frame, AutoreleasePool executes a release operation on the frame and the reference count is 1. The object inherits from each other. When the reference count is 0 when the node is removed next time, it is automatically released. In this way, the automatic memory management of the Ref object is realized.


However, whether it is the smart pointer in C ++ 11 or the "smart pointer" of the body in the Cocos2d-x, the programmer needs to manually declare that it is "intelligent:


Shared_ptr np1 (new int (); // C ++ 11 declares a smart pointer
Auto node = (new Node ()-> autorel (); // declare "smart pointer" in Cocos2d-x"


To simplify this declaration, the Cocos2d-x uses the static create method () to return a "smart pointer" object, and most of the classes in the Cocos2d-x can return a "smart pointer" Through create ", for example, Node and Action. At the same time, our custom UI elements should follow this style to simplify their declaration:


Node * Node: create (void)
{
Node * ret = new Node ();
If (ret & ret-> init ()){
Ret-> autorelease ();
}
Else {
CC_SAFE_DELETE (ret );
}
Return ret;
}


3.2.5.3 AutoreleasePool queue


For some game objects, the life cycle of a single frame is obviously too long. Assume that a single frame calls 100 methods, and each method creates 10 smart pointer objects, and these objects are used only within the scope of each method, the maximum memory peak value at the end of the frame is the memory occupied by 1000 game objects, in this way, the average memory usage of the game will be greatly increased. In fact, each frame requires only 10 objects of memory on average, assuming these methods are executed in sequence.


By default, AutoreleasePool is cleared once every frame. It is mainly used to clear UI elements. Because most of the UI elements are added to the UI tree, they will always occupy the memory, in this case, the cleanup of each frame does not have much impact on memory usage.


Obviously, for custom data objects, we need to be able to customize the lifecycle of AutoreleasePool. The Cocos2d-x implements the custom [Reference 5] of the "smart pointer" lifecycle by implementing an AutoreleasePool queue and the PoolManager manages this AutoreleasePool queue:


Class CC_DLL PoolManager
{
Public:
Static PoolManager * getInstance ();
Static void destroyInstance ();


AutoreleasePool * getCurrentPool () const;
Bool isObjectInPools (Ref * obj) const;


Friend class AutoreleasePool;


Private:
PoolManager ();
~ PoolManager ();


Void push (AutoreleasePool * pool );
Void pop ();


Static PoolManager * s_singleInstance;


Std: deque _ releasePoolStack;
AutoreleasePool * _ curReleasePool;
};


PoolManager initially and by default has at least one AutoreleasePool, which is primarily used to store the UI element objects in the Cocos2d-x described earlier. We can create our own AutoreleasePool object and press it to the end of the queue. However, if we use the new operator to create the AutoreleasePool object, we need to manually release it. In order to achieve the effect of using automatic variables with smart pointers to manage the memory, the Cocos2d-x performs special processing on the Structure and destructor of AutoreleasePool so that we can manage memory release through automatic variables:


AutoreleasePool: AutoreleasePool ()
: _ Name ("")
# If defined (COCOS2D_DEBUG) & (COCOS2D_DEBUG> 0)
, _ IsClearing (false)
# Endif
{
_ ManagedObjectArray. reserve (150 );
PoolManager: getInstance ()-> push (this );
}


AutoreleasePool: AutoreleasePool (const std: string & name)
: _ Name (name)
# If defined (COCOS2D_DEBUG) & (COCOS2D_DEBUG> 0)
, _ IsClearing (false)
# Endif
{
_ ManagedObjectArray. reserve (150 );
PoolManager: getInstance ()-> push (this );
}


AutoreleasePool ::~ AutoreleasePool ()
{
CCLOGINFO ("deallocing AutoreleasePool: % p", this );
Clear ();


PoolManager: getInstance ()-> pop ();
}


AutoreleasePool adds its own pointer to the AutoreleasePool queue of PoolManager In the constructor, and removes itself from the queue in the destructor. Because of the Ref: autorelease () described earlier () always add yourself to "Current AutoreleasePool". If the current AutoreleasePool is always an element at the end of the queue, declaring an AutoreleasePool object will affect the subsequent objects until the AutoreleasePool object is removed from the queue. In this way, we can use this in the program:


Class MyClass: public Ref
{
Static MyClass * create (){
Auto ref = new MyClass ();
Return ref-> autorelease ();
}
}


Void customAutoreleasePool ()
{
AutoreleasePool;
Auto ref1 = MyClass: create ();
Auto ref2 = MyClass: create ();
}


When this method starts to be executed, declare an automatic variable pool of the AutoreleasePool type, and its constructor will add itself to the tail end of the AutoreleasePool queue of the PoolManager, next, ref1 and ref2 will be added to the pool. When the method ends, the lifecycle of the pool automatic variable ends, and the Destructor will release the object, remove yourself from the queue.


In this way, we can control the lifecycle of the smart pointer in the Cocos2d-x by customizing the lifecycle of the AutoreleasePool.


3.2.5.4 Summary


Cocos2d-x has an efficient and sophisticated memory management mechanism, which is essentially a variant of "smart pointer. It declares a "smart pointer" Through Ref: autorelease and avoids the programmer's statement of "smart pointer" by encapsulating autorel in the create method, by default, AutoreleasePool clears all "smart pointer objects" at the end of a frame, and we can customize the scope of AutoreleasePool.


Combined with the Cocos2d-x memory management mechanism and characteristics, this section summarizes some precautions for using Cocos2d-x memory management:
1. The Ref reference count is NOT thread-safe. In multithreading, we need to handle mutex locks to ensure thread security. In Objective-C, because AutoreleasePool is implemented by a language-level system, each thread has its own AutoreleasePool queue.
2. For the subclass of the custom Node, add the create method for the class. This method returns an autorelease object.
3. If the automatic data type needs to be dynamically allocated memory, it inherits from Ref and adds the create static method to return the autorelease object.
4. Only the Ref object used within a method is used. The user-defined AutoreleasePool is used to instantly clean up memory usage.
5. Do not dynamically allocate AutoreleasePool objects. Always use automatic variables.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.