Files
mlib/doc/ISSUES.org
T
2019-01-21 22:06:31 +01:00

12 KiB

Long term issues


DONE #22: Enhanced services for SPSC Queue   ENHANCEMENT

Add services:

DONE _push_bulk

Test the capacity of the queue and push as much as possible in the queue with one check of the atomic structure.

DONE _pop_bulk

Test the capacity of the queue and push as much as possible in the queue with one check of the atomic structure.

DONE _push_force:

If the capacity of the queue is full, pop one element and push it: push always succeed and the queue always keep the youngest element.

TODO #21: Generic Binary serialization   ENHANCEMENT

Based on issue #26 of https://github.com/P-p-H-d/mlib/issues/26

Some kind of "binary serialization" on the model of get_str/parse_str could be possible. It would be a great feature from the application point-of-view: binary representation is more bandwidth-efficient if used on network communications.

It will be good to have import/export methods to the XML/JSON/MSGPACK/PROTOBUF/BINARY format. However, adding all of them on by one in the M*LIB containers doesn't seem satisfactory.

Instead, adding a generic interface for the serialization of data that may be customized by the user to perform the import/export of objects in whatever format they want into what they want (FILE/memory/…). To simplify it, this interface could only support one kind of import/export per compilation unit.

Problem: the interface is either too high level (limiting too much what we can do) or too low level (exporting too many internal things of the library).

TODO #20: Bucket priority queue   ENHANCEMENT

Add a new kind of priority queue. See https://en.wikipedia.org/wiki/Bucket_queue

Check if it will be better as intrusive or non-intrusive container.

To test if a bucket is empty or not, a bitfield can be used to check if the bit associated to the bucket is set or not. To get the highest bucket non empty, we can perform a CLZ of the bitset, which shall be much faster than performing a linear search of the buckets (algorithm complexity is the same, except that we can scan 64 entries at a time).

Check if we can use BITSET, or introduce fixed size BITSET or use ad-hock implementation.

TODO #19: Intrusive Red Black Tree   ENHANCEMENT

Add intrusive red black tree.

TODO #18: Missing methods   ENHANCEMENT

Some containers don't have all the methods they should. See the cells in yellow here: http://htmlpreview.github.io/?https://github.com/P-p-H-d/mlib/blob/master/doc/Container.html

TODO #17: Ressource handler   ENHANCEMENT

A ressouce handler which shall associated a unique handle to a ressource. The handle shall be unique for the ressource and shall not be reused. It is typically a 64 bits integers always incremented (even if the program creates one billion ressources per second, the counter won't overflow until 585 years).

The ressource handler shall make an association between a HANDLE 64 bits and:

  • how much real owners claim to own the ressource (the ressource is only owned by the ressource handler, however it acts as a delegate of the real owner),
  • how much users keep a pointer to the ressource.
  • pointer to the resource itself. This may be a better alternative than shared_ptr & weak_ptr:
  • reduce fragmentation,
  • no cycle dependencies,
  • shared_ref & weak_ref becomes only HANDLE,
  • all ressources can be freed in one pass.

    Needs lock free dictionnary or at least concurrent dictionnary.

    How to handle multiple resource ?

    • variant: works fine. Memory usage can be (much) higher than needed if there is a lot of dissimilarity between the size of the objects.
    • embedded the type in the ressource handler: more work, API more complex. Memory usage seems better.

TODO #16: Lock Free List   ENHANCEMENT

Implement a lock free list. Most of the difficulty is the memory reclamation part. Typically this lock free list shall be compatible with RCU method.

First step: backoff methods   DONE

Second step: lock free node pool :   DONE

Done as m-c-mempool header.

Third step: Implement generic lock free list on top of it.

The ABA problem is already taken into account by the memory alloctor provided that the lock free list doesn't try to be smart.

backoff has be used when using CAS.

Concurrent insertion / insertion and insertion / deletion and deletion / deletion shall be taken into account.

Questions:

  • singly or doubly or dual push?
  • needs to be logically deleted : needs a previous field (NULL if not logically deleted) ? TBC

TODO #15: Constructor / Destructor Prologue / Epilogue for Stack Exception Handling   ENHANCEMENT

Constructor (and destructor) need to use user-defined prologue / epilogue. This is in order to register the constructed object into a proper Stack Exception Handling so that exceptions throwing may work reliably.

Proposal:

  • M_CONSTRUCTOR_PROLOGUE(object, oplist);
  • M_CONSTRUCTOR_EPILOGUE(object, oplist);
  • M_DESTRUCTOR_PROLOGUE(object, oplist);
  • M_DESTRUCTOR_EPILOGUE(object, oplist);

Object creation will need to add all sub-objects into the stack, then unstack all to push instead the root object (which will recursively remove them).

See also http://freetype.sourceforge.net/david/reliable-c.html#cseh

TODO #14: Memory allocation enhancement   ENHANCEMENT

Enhancement of the memory allocation scheme to find way to deal properly with advance allocators:

  • non-default alignment requirements for types,
  • instance-based allocator (may need instance based variable access),
  • expected life of created type (temporary or permanent),
  • stack based allocator,
  • global variable access for allocator,
  • maximum allocation before failure.

Most of theses are already more or less supported. Examples shall be created to show how to deal with this:

  • alignement shall be implemented with the attributes of <stdalign.h>

However I sill don't know how to implement "instance-based allocator" which is what is missing. The problem is how to give to methods context local information store within the container itself.

Update:

API transformation support enables "instance-based allocator" to be made easily. Needs some formal operator in the oplist to support it fully and an example.

Can be supported using another API extension, some more operators and forcing some names:

  • API_N: call like FUNC(obj->extra_data, type)

'obj' is a forced named corresponding to an alias to an object in the function. Operator needed:

  • EXTRA_DATA: Add an extra-data field wihtin the container. Defines the type of data.

TODO #12: Atomic shared pointer   ENHANCEMENT

Add an extension to the SHARED_PTR API:

  • New type atomic_shared_ptr
  • name_init_atomic_set (&atomic_shared_ptr, shared_ptr);
  • name_init_set_atomic (shared_ptr, &atomic_shared_ptr);
  • name_init_atomic_set_atomic (&atomic_shared_ptr, &atomic_shared_ptr);
  • name_atomic_set (&atomic_shared_ptr, shared_ptr);
  • name_set_atomic (shared_ptr, &atomic_shared_ptr);
  • name_atomic_set_atomic (&atomic_shared_ptr, &atomic_shared_ptr);
  • name_atomic_clear

No _ref or direct _init: we need to init first a normal shared_ptr then the atomic (TBC)

_atomic_set method:

It can be implemented by incrementing the non atomic shared pointer reference, then performs a compare_and_swap to the data of the atomic shared pointer, finally decrement and dec/free the swapped previous data of the atomic .

_set_atomic method:

It needs to perform the following atomic operation : <read the pointer, deref pointer and increment the pointed value> I don't known how to do it properly.

See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4162.pdf

Proposition for _set_atomic we store temporary NULL to the atomic_ptr struct to request an exclusive access to the data (this looks like a lock and other operations need to handle NULL) :

        void shared_ptr_set_atomic(ptr a, atomic_ptr *ptr)
        {
          // Get exclusive access to the data
          p = atomic_load(ptr);
          do {
            if (p == NULL) {
              // TODO: exponential backoff
              p = atomic_load(ptr);
              continue;
            }
          } while (!atomic_compare_exchange_weak(ptr, &p, NULL));
          // p has exclusive access to the pointer
          p->data->cpt ++;
          a->data = p->data;
          atomic_exchange (ptr, p);
        }

This prevents using NULL which obliges atomic shared pointer to point to a created object…

Other alternative solution is to use the bit 0 to mark the pointer as being updated, preventing other from using it (TBC only clear):

        void shared_ptr_set_atomic(ptr a, atomic_ptr *ptr)
        {
          // Get exclusive access to the data
          p = atomic_load(ptr);
          do {
            if ( (p&1) != 0) {
              // TODO: exponential backoff
              p = atomic_load(ptr);
              continue;
            }
          } while (!atomic_compare_exchange_weak(ptr, &p, p|1));
         // Exclusive access (kind of lock).
          p->data->cpt ++;
          a->data = p->data;
          atomic_set (ptr, p);
        }

Other implementation seems to have it hard to be lock-free: cf. https://github.com/llvm-mirror/libcxx/commit/5fec82dc0db3623546038e4a86baa44f749e554f

TODO #5: Concurrent dictionary Container   ENHANCEMENT

Implement a more efficient dictionary than lock + std dictionary for all operations when dealing with threads. See https://msdn.microsoft.com/en-us/library/dd287191(v=vs.110).aspx

Multiple locks within the dictionnary

A potential implementation may be to request at initialization time the number of concurrent thread N. Create a static array of N dictionnary with N mutex. Then to access the data will perform :

  • compute hash of object,
  • access high bits of hash and select which dictionnary shall have the data,
  • lock it,
  • perform classic access to the data (check if the compiler can properly optimize the hash computation),
  • unlock it.

The property of the hash shall allow a good dispersion of the data across multiple locks, reducing the constraints on the lock. This implementation could be build easily upon the already existent dictionary.

To test.

See also https://github.com/simonhf/sharedhashfile

Lock Free dictionnary

Evaluate also lock-free dictionary (easier with open addressing). It needs a complete rewrite of the inner loop through. The hard part is the dynamic resizing of the internal array (see http://preshing.com/20160222/a-resizable-concurrent-map/ for a potential solution and http://www.cs.toronto.edu/~tomhart/papers/tomhart_thesis.pdf for memory reclamation techniques). See also https://www.research.ibm.com/people/m/michael/spaa-2002.pdf https://www.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.2017.11.22a.pdf persents different techniques used by linux kernel. It needs before lock-free list: http://www.cse.yorku.ca/~ruppert/papers/lfll.pdf http://www.cse.yorku.ca/~ruppert/Mikhail.pdf

Context

The best parallel algorithm is still when there is as few synchronization as possible. A concurrent dictionary will fail at this and will result in average performance at best. The typical best case will be in RCU context (a lot of readers, few writers), so the interface shall be compatible with such structure.

Linked list

Another solution is to create a huge list of items which is:

  • atomically updated,
  • in reverse order of the hash (bit 0 is highest bit, Bit 63 is the lowest bit).

Hash table will only give quick access to items to this list. Expanding the table won't change the order of the list (so the items will remain at the same place and always accessible by other threads) but just add sentinel in the sentinel at the right place (there won't be any reallocation of the list). Inserting will need to insert an item at the right place. Not sure it will really help. To analyse.

Concurrent dictionary is possible now with CONCURRENT_DEF + DICT_DEF. But it uses a global lock for all dictionary access.

Open Addressing Indirect Table with a lock free node pool used as transaction pool.

A good way maybe Open Addessing table used only for indirection and a freelist memory reclamation container for handle the entries (like a transaction).