Item 50: understand when to replace New and delete.
By Scott Meyers
Translator: fatalerror99 (itepub's nirvana)
Release: http://blog.csdn.net/fatalerror99/
Let's review the basics first. Why do some people want to replace the operator new or operator Delete version provided by the compiler? There are three main reasons:
- To monitor usage errors.If you do not delete the memory generated by new, memory leakage will occur. Executing more than one delete operation on the new memory triggers undefined behaviors. If operator new saves a list of allocated addresses, and operator delete removes the addresses from the list, it is easy to detect the above usage error. Similarly, a programming error may cause data overruns (Data overflow) (written after the end of an allocated block) and underruns (underruns) (write before the beginning of an allocated block ). Before and after the customer's available memory, the custom operator news can span the allocation block and place known byte patterns ("signatures") in these spaces "). Operator deletes checks whether these signatures remain unchanged. If not, an overflow or underflow occurs at a certain time during the survival period of the allocated block, and operator deletes can record this and the value of that nasty pointer.
- To improve performance.Operator new and operator Delete versions loaded by the compiler are designed for multiple purposes. They must be accepted by long-running programs (such as Web servers), but they must also be accepted by programs that run for less than one second. They must process large memory blocks, small memory blocks, and a hybrid request sequence. They must adapt to a wide range of allocation modes, from the dynamic distribution of a few blocks that exist throughout the program's duration to the continuous distribution and release of a large number of short-lived objects. They must be responsible for the heap fragmentation. If this process is not controlled, it will eventually fail to satisfy the requests for large memory blocks, even if there is enough free memory distributed in a large number of small blocks.
Due to the specific requirements of the Memory Manager, it is not surprising that operator news and operator Deletes loaded by the compiler adopt the middle-of-the-road strategy (intermediate route policy. Their work is right for everyone, but not for anyone. If you fully understand the dynamic memory Application Mode of your program, you may often find that the custom versions of operator new and operator Delete are better than the default version. For "better than", I mean they run faster-sometimes an order of magnitude increases-and they need less memory-up to less than 50%. For some (though not all) applications, replacing common new and delete with custom versions is an easy way to achieve significant performance improvement.
- To collect statistics on usage.It is wise to collect information about how your software uses dynamic memory before writing custom news and deletes. How is the size of the allocated block distributed? What is the distribution of lifetime? Their distribution and release sequence tends to be FIFO ("first in, first out") ("first in, first out"), or LIFO ("Last in, first out ") ("after-going, first-out") is the order close to a random order? Will the usage mode change over time? For example, does your software have different distribution/release modes in different stages of operation? What is the maximum value of dynamically allocated memory (that is, its "Highest Water Level") used in any time? The custom versions of operator new and operator Delete make it easy to collect such information.
In terms of concept, writing a custom operator new is quite simple. For example, this is a major part of global operator new that facilitates under-and overruns detection. There are a lot of small troubles here, but we will immediately pay attention to them.
Static const int Signature = 0 xdeadbeef;
Typedef unsigned char byte;
// This code has several flaws-see below
Void * operator new (STD: size_t size) Throw (STD: bad_alloc)
{
Using namespace STD;
Size_t realsize = size + 2 * sizeof (INT); // increase size of request SO2
// Signatures will also fit inside
Void * pmem = malloc (realsize); // call malloc to get theactual
If (! Pmem) Throw bad_alloc (); // memory
// Write signature into first and last parts of the memory
* (Static_cast <int *> (pmem) = signature;
* (Reinterpret_cast <int *> (static_cast <byte *> (pmem) + realsize-sizeof (INT) =
Signature;
// Return a pointer to the memory just past the first signature
Return static_cast <byte *> (pmem) + sizeof (INT );
}
Most of the defects of this operator new are related to its failure to follow the C ++ convention of the function called this name. For example, item 51 clarifies that all operator new should contain a loop that calls the New-handling function, but it does not exist here. However, item 51 is dedicated to such conventions, so I will ignore them here. I want to pay attention to a more subtle problem:Alignment(Arrange and align ).
Many Computer Architectures require that specific types of data be stored in memory addresses of a specific nature. For example, a architecture may require that pointers (pointer) appear on a four-fold address (that is, aligned by four bytes) or doubles (double-precision floating point type) it must appear on an address multiple of eight (that is, alignment according to the eight-character section ). Failure to comply with such constraints will cause hardware exceptions at runtime (runtime hardware exception ). Other architectures may be more tolerant, but better performance can be achieved if the order of arrangement and alignment is satisfied. For example, in intel X86 architecture, doubles (dual-precision floating point type) can be arranged according to any byte boundary, but if they are aligned by Octa, the access speed will be much faster.
Alignment (permutation and alignment) is significant here because C ++ requires all operator news to return pointers suitable for any sort of data. Malloc also works under the same requirements, so it is safe to let operator new return its pointer from malloc. However, in the operator news above, we did not return the pointer we obtained from malloc. the pointer we returned is an int larger than the pointer we obtained from malloc. This cannot be guaranteed to be safe! If the customer calls operator new as a double (or, if we are writing operator new [], an array of doubles) to apply for enough memory, furthermore, if we are running an ints machine with four bytes and doubles needs eight-character knots alignment, we may return an improper alignment pointer. This can cause program crash. Or, it only causes the running speed to slow down. In either case, this may not be what we want.
Details such as alignment can be used to differentiate Specialized memory managers and things hastily pieced together by programmers who are upset about other tasks. Writing a user-defined Memory Manager that can almost work is quite easy. Writing a job is much more difficult. As a general rule, I suggest you do not commit yourself to this unless you have.
In many cases, you do not have. Some compilers provide option switches to enable debugging and recording for their memory management functions. A quick look at your compiler documentation may eliminate the idea of writing new and delete. On many platforms, commercial products can replace the memory management functions provided with the compiler ). All you need to do is to relink them in order to take advantage of their enhanced functions and (maybe) better performance. (Of course, you must also buy them back .)
Another option is open-source memory manager. They can be used on multiple platforms, so you can download and try them out. The pool library from boost (see item 55) is such an open source distributor. The pool Library provides a tuning distributor for one of the most common cases that can help with custom memory management (allocation of a large number of small objects (small objects. Many C ++ books, including early versions of this book, demonstrate the code of a high-performance small-object Allocator (high-performance small object distributor, however, they usually ignore the portability, arrangement and alignment considerations, thread security, and other such troublesome details. The real library will pay attention to the use of much more robust code. Even if you decide to write your own news and deletes, it is likely that the open-source version will provide you with insights into the easy-to-ignore details that "differentiate between actually and practically works. (It is known that alignment (arrange alignment) is such a detail, it is worth mentioning that tr1 (see item 54) including support for found type-specific arrangement and alignment requirements .)
The topic of this item is to understand when to replace the default versions of new and delete (whether global or per-class. We should summarize the timing issue in more detail than previously.
- To monitor usage errors(As before ).
- To collect statistics on the use of dynamically allocated memory(As before ).
- To speed up allocation and recovery.General-purpose allocators (a universal purpose distributor) is generally (though not always) much slower than a custom version, especially if the custom version is specially designed for a specific type of objects. Class-specific allocators (dedicated class distributor) is a typical application of fixed-size allocators (fixed size distributor) (like those provided by boost pool library. If your program is single-threaded and your Compiler's default memory management routine is thread-safe, by writing thread-unsafe allocators (non-thread security distributor), you can get a considerable increase in speed. Of course, before you come to the conclusion that operator new and operator Delete are valuable for speed improvement, determine your program to ensure that these functions are real bottlenecks.
- To reduce the space cost of default memory management.General-purpose memory managers (general purpose Memory Manager) usually (though not always) not only slower than the custom version, but also more memory is often used. This is because they often incur certain costs for each allocated block. The splitters for small objects (small objects) tuning (such as those in the boost pool Library) fundamentally eliminate such costs.
- To adjust the improper arrangement and alignment of the default distributor.As I mentioned earlier, in the X86 architecture, doubles performs the fastest access speed when it is aligned according to the eight-character section. Some operator news provided by the compiler cannot ensure that the dynamic distribution of doubles is aligned in octal. In this case, the program performance can be greatly improved by replacing the default version with operator new, which ensures the alignment according to the eight-character section.
- To aggregate related objects, make them closer to each other.If you know that specific data structures (data structures) are usually used together, and you want to minimize the frequency of page errors when working on the data, it makes sense to create an independent heap (HEAP) for these data structures so that they can be aggregated on as few pages as possible. The placement versions of new and delete (see item 52) makes it possible to complete such aggregation.
- To get unusual behaviors.Sometimes you want operators new and delete to do something that the compiler version does not provide. For example, you may want to allocate and recycle blocks in the shared memory, but you can only use one c api to manage the memory. Write the custom version of the new Delete Statement (maybe placement versions -- See item 52 again). You can use C ++ to hide the c api. As another example, you can write a custom operator delete that uses zeros to rewrite the recycled memory to improve the security of application data.
Things to remember
- There are many legitimate reasons for writing custom versions of new and delete, including improving performance, debugging heap usage errors, and collecting heap usage information.