Efficient use of multiple valves

I am learning how to switch my allocation method from the simplified overload of new to using multiple allocators through a code base. However, how can I use multiple spreaders efficiently? The only way I could develop in my research was for the distributors to be global. Although this seemed to have problems, since it is usually a "bad idea" to use many global variables.

I want to learn how to efficiently use multiple dispensers. For example, I can use one distributor only for a specific subsystem and another distributor for another subsystem. I'm not sure that the only way to do this is to use several global distributors, so I hope for a better understanding and design.

+6
source share
3 answers

In C ++ 2003, the dispenser model is broken, and there really is no right solution. For C ++ 2011, the distributor model has been fixed, and you can have instances for each distributor that propagate to contained objects (unless, of course, you want to replace them). Generally, for this to be useful, you probably want to use the dynamically polymorphic type of distributor, for which std::allocator<T> not required by default (and normally I would expect it to not be dynamically polymorphic, although this may be the best implementation choice). However, [almost] all the classes in the C ++ standard library that take up memory allocation are templates that use the allocator type as the template argument (for example, IOStreams are an exception, but usually they do not allocate any interesting amount of memory to guarantee adding dispenser support).

In a few of your comments, you insist that distributors really have to be global: this is definitely not true. Each type that supports the allocator saves a copy of the provided allocator (at least if it has instance level data, and if not, there is nothing that could be saved, for example, in the case of the default allocator using operator new() and operator delete() ). This actually means that the distribution mechanism provided to the object must adhere as long as it uses the active dispenser. This can be done using a global object, but it can also be done using, for example, reference counting or linking a distributor to an object containing all the objects to which it is defined. For example, if each "document" (I think XML, Excel, Pages, any structure file) transfers the distributor to its members, the distributor can live as a member of the document and be destroyed when the document is destroyed after its contents are destroyed. This part of the distributor model should work with classes pre-C ++ 2011 if they also accept a dispenser argument. However, in pre-C ++ 2011 classes, the allocator will not be passed to contained objects. For example, if you provide a allocator for std::vector<std::string> , C ++ 2011 will create std::string using the allocator assigned by std::vector<std::string> , appropriately converted to work with std::string s. This will not happen with pre-C ++ 2011 allocators.

To actually use distributors in a subsystem, you really need to pass them, either explicitly as an argument to your functions and / or classes, or implicitly using objects that support the distributor, which serve as context. For example, if you use any of the standard containers as [part of] the transferred context, you can get the used allocator using its get_allocator() method.

+8
source

You can use the new placement. This can be used either to indicate the memory area or to overload the static void* operator new(ARGS) . Globals are not required, and a really bad idea is here if efficiency is important and your problems require. Of course, you will need to hold onto one or more distributors.

The best you can do is understand your problems and create strategies for your distributors based on patterns in your program and actual use. The overall goal of malloc very good at what it does, so always use this as one baseline to measure against. If you do not know your usage patterns, your allocator will most likely be slower than malloc .

Also keep in mind that these types that you use will lose compatibility with standard containers unless you use a global or stream local and user dispenser for standard containers, which quickly defeats the target in many contexts. An alternative is also to create your own dispensers and containers.

+2
source

Some uses for multiple allocators include reduced CPU usage, reduced fragmentation, and fewer cache misses. Thus, the decision really depends on what type and where is the bottleneck of your placement.

CPU usage will be improved due to the absence of locks for active threads, which eliminates synchronization. This can be done in a memory allocator with local thread storage.

Fragmentation will be improved due to the fact that allocation with different lifetimes will be allocated from different heaps - allocating background I / O in a separate heap from the user's active task ensures that the two will not mix each other. Most likely, this is done using the stack for your heaps and pressing / popping up when you are in different functional areas.

Cache gaps will be improved by keeping distributions within the system together. If Quadtree / Octree is allocated from its own heap, this ensures the availability of locations based on frustrum requests. This is best done by overloading the new operator and the delete operator for specific classes (OctreeNode).

+1
source

Source: https://habr.com/ru/post/910423/


All Articles