3

我正在研究使用许多小型临时对象的并发数据结构。许多这些对象的大小相同。因此,为了减少内存分配器的压力,我一直在使用线程本地映射将对象存储为<size, list>元组。

当一个线程需要一个对象时,它会在去分配器之前检查映射是否有合适的对象。这工作得很好并且显着提高了性能,但是它很容易出现超时一个线程将其整个池丢失给其他线程的问题,从而迫使它分配新对象。如果应用程序运行很长时间,我会发现一些线程的内存池很大。

为了解决这个问题,我想在线程本地池和分配器之间添加一个共享内存池。由于结构的数量和结构的大小在编译时是恒定的,我认为应该以某种方式使用宏将每个大小映射到数组位置。允许更轻松的内存管理。

这是我目前的解决方案

#define RC_OBJECT_COUNT 0
#define ADD_RC_OBJECT(object) \
    #ifndef RC_MAP_``sizeof(object)''
        #define RC_OBJECT_TEMP RC_OBJECT_COUNT \
        #undefine RC_OBJECT_COUNT \
        #define RC_OBJECT_COUNT RC_OBJECT_TEMP+1 \
        #define RC_MAP_``sizeof(object)'' RC_OBJECT_TEMP
    #endif

有没有办法将调用 sizeof(object) 的结果回显到定义的变量名中?最好没有单独的配置脚本。

4

1 回答 1

0

我编写的代码有点像你正在讨论的,但我并没有像你试图做的那样使用预处理器宏。

我的代码有一个带有小 API 的“对象管理器”。我的程序中想要获取对象的任何部分都会调用一个 API 函数,即“注册函数”,它表示“我想请求具有以下特征的对象”。注册函数返回一个句柄。然后,有一个函数GetObject()将句柄作为参数,并返回一个指向对象的指针。当代码处理完对象后,会有一个函数ReleaseObject()接受一个指向该对象的指针。

For each different object there is a linked list that I call the "ready list". The code always inserts and removes from the head of the list (since one uninitialized object is as good as another; they are all the same size). My code is single-threaded so I don't have any locking issues, but for multithreaded I would need to put a lock on each ready list. (But it's very fast to insert or remove at the head of a linked list so no thread would need the lock very long.)

For my purposes, different parts of my program could share objects, so I had a reference count on each object. ReleaseObject() would decrement the reference count, and when it went to zero, would put the object on the appropriate ready list.

The handle returned by GetObject() is really just a pointer to the linked list structure.

In my code, if there is a call to GetObject() and the ready list is empty, then malloc() is called automatically and a new object created and returned. The register function takes a pointer to a function to call to create an object with malloc(), a pointer to a function to free an object with free(), and a pointer to a "sanity check" function (since I like my programs to check themselves at runtime with calls to assert()), and any arguments like size of object.

If multiple parts of my program register that they want the same kind of object, the object manager notices this and just returns a handle to the ready list that was already set up by the first call to register. Now they are sharing objects in a single ready list.

This may sound complicated but it didn't take me long to build and it works very well. If there is no object on the ready list, the object manager knows to just call the function pointer stored in the ready list struct, to get a new object, and then it returns that.

The most common bug I have found in my programs: failure to call ReleaseObject() when done with the object. Then the program has a memory leak and calls malloc() a lot, and on an embedded platform runs out of memory and dies. Usually it's very easy to notice this, and add in an appropriate call to ReleaseObject().

EDIT: (In response to a question in the comments) The object manager keeps an array of different object management struct instances. Each struct stores three function pointers: pointer to a "new" function, a pointer to a "delete" function, a pointer to a "sanity check" function; a handful of values that are passed to the "new" function when it is called (for example, size of a desired buffer); and the head of the linked list of objects. When code calls the "register" function, the object manager checks to see if any spot in this array has identical values from "register" (the 3 function pointers and the handful of values). If the identical values are found, the object manager returns a pointer to that object manager struct instance. If the identical values are not found, the object manager copies those values into the next available struct in the array and returns a pointer to that.

This means that my "register" function is O(N) in the number of different kinds of objects being managed, but for my app there are only about 4 different kinds of objects so I never tried to optimize this. My "get" function is O(1), as it has a pointer right to the correct object manager struct, and removing from the head of a linked list is a constant-time operation.

The array of object manager structs is allocated by malloc() and if additional object types are registered the code can call realloc() to grow the memory.

In my application, I haven't had the need for an "unregister" operation, but if there were one, it would involve freeing all the objects on that ready list, and marking that spot in the object manager array as unused.

My app is an audio processing engine, and it never wants to call malloc() while processing audio because malloc() might decide to re-organize the free blocks list or something and take a while, and the audio playback might glitch. The engine has an "init" phase before starting playback where the code calls the "register" function and all the audio buffers get allocated; then at runtime the buffers just fly off and onto the ready lists. It really works quite well, and I have run it on low-power DSP chips with no problems.

于 2013-10-29T19:27:21.260 回答