Varon-T Documentation Release 2.0.1-Dev-5-G376477b
Total Page:16
File Type:pdf, Size:1020Kb
Varon-T Documentation Release 2.0.1-dev-5-g376477b RedJack, LLC June 21, 2016 Contents 1 Contents 3 1.1 Introduction...............................................3 1.2 Disruptor queue management......................................3 1.3 Value objects...............................................5 1.4 Producers.................................................6 1.5 Consumers................................................7 1.6 Yield strategies..............................................9 1.7 Example: Summing integers.......................................9 2 Indices and tables 15 i ii Varon-T Documentation, Release 2.0.1-dev-5-g376477b This is the documentation for Varon-T 2.0.1-dev-5-g376477b, last updated June 21, 2016. Contents 1 Varon-T Documentation, Release 2.0.1-dev-5-g376477b 2 Contents CHAPTER 1 Contents 1.1 Introduction Message passing is currently a popular approach for implementing concurrent data processing applications. In this model, you decompose a large processing task into separate steps that execute concurrently and communicate solely by passing messages or data items between one another. This concurrency model is an intuitive way to structure a large processing task to exploit parallelism in a shared memory environment without incurring the complexity and overhead costs associated with multi-threaded applications. In order to use a message passing model, you need an efficient data structure for passing messages between the processing elements of your application. A common approach is to utilize queues for storing and retrieving messages. Varon-T is a C library that implements a disruptor queue (originally implemented in the Disruptor Java library), which is a particularly efficient FIFO queue implementation. Disruptor queues achieve their efficiency through a number of related techniques: • Objects are stored in a ring buffer, which uses a fixed amount of memory regardless of the number of data records processed. • Objects are stored directly inline in the slots of the ring buffer, and their life cycle is controlled by the disruptor queue, not the application. This eliminates any per-record memory allocation overhead. • The ring buffer’s storage is implemented as a regular C array, so the data instances are all adjacent to each other in memory. This allows us to take advantage of cache striding and locality. • In most cases, the producers and consumers of a queue are coordinated without needing any costly locks, or even atomic CAS operations. Instead, they only require relatively cheap memory barriers. • Multiple consumers are able to drain a single queue, even when there’s a temporal constraint on the consumers — i.e., where a “downstream” consumer must wait to process a particular record until an “upstream” consumer has finished with it. By sharing a single queue, you eliminate additional memory allocations and copies. 1.2 Disruptor queue management A disruptor queue in Varon-T is a high-performance ring buffer with memory barriers and yielding strategies to coor- dinate producers and consumers of value instances. A distinguishing feature of disruptor queues is that value instances are pre-allocated by the library, usually with size of a power of 2. The result is disruptor queues do little to no memory allocation during execution, and your application requests access from the library to value instances. A Varon-T distruptor queue is defined by the following interface: struct vrt_queue 3 Varon-T Documentation, Release 2.0.1-dev-5-g376477b const char *name An alphanumeric name for the queue. struct vrt_value **values The array of values managed by the queue (see value objects for a discussion of custom values.) unsigned int value_mask A mask that is always equal to |queue| - 1 and used to determine the queue size efficiently through the calculation x % value_count. This works because the actual queue size is always a power of 2. const struct vrt_value_type *value_type The type of values managed by the queue (see value objects for a discussion of custom value types.) vrt_producer_array producers vrt_consumers_array consumers Arrays of producers and consumers feeding this queue vrt_value_id last_consumed_id The ID of the last guaranteed value instanced processed by each consumer. struct vrt_padded_int last_claimed_id The ID of the last value instance claimed by a producer. This is only updated for disruptor queues with multiple producers. It is expected that single producer disruptor queues will track this value internal to the producer instance. vrt_padded_int cursor The next value instance ID that can written into the queue. 1.2.1 Built-in operations We support several built-in operations for queue management. struct vrt_queue * vrt_queue_new(const char *name, const struct vrt_value_type *value_type, unsigned int value_count) Construct a new disruptor queue called name that stores value_count objects of type value_type in a ring buffer. void vrt_queue_free(struct vrt_queue *q) Free the memory associated with q. static inline vrt_value_id vrt_queue_get_cursor(struct vrt_queue *q) Return the ID of the value instance that was most recently published into the queue. Since this function involves a memory barrier, it should be used sparingly. #define vrt_queue_size(q) Return the number of values managed by the queue. #define vrt_queue_get(q, id) Return the value instance with the given ID 1.2.2 Built-in result codes The following result codes are used to indicate various disruptor queue states. VRT_QUEUE_EOF Signify that no more data will be sent through the queue. VRT_QUEUE_FLUSH Signify that an upstream producer has requested a flush operation. 4 Chapter 1. Contents Varon-T Documentation, Release 2.0.1-dev-5-g376477b 1.3 Value objects Each Varon-T disruptor queue manages a list of values, which are allocated and controlled by the disruptor queue. This increases performance of the queue and application by eliminating the need to perform allocations on a per object basis. Another way to think of the disruptor queue is a pool of value objects available to your applications. The value interface is simple and is a superclass of a value managed by a Varon-T disruptor queue. struct vrt_value An oqaque type that serves as a superclass for ring buffer values. Each value type in an application must implement the following interface: struct vrt_value_type cork_hash type_id A type identifier for this value type. The cork-hash utility in libcork will generate a sufficient hash value for this field given a string identifier. struct vrt_value *(*new_value)(const struct vrt_value_type *type) Allocates, iniatializes, and returns an instance of type. void (*free_value)(const struct vrt_value_type *type, struct vrt_value *value) Frees any resources used by value, which must be an instance of type. 1.3.1 Example: Integer values The following is a simple implementation of a new value type for storing integer values in a Varon-T disruptor queue. #include <libcork/core.h> struct vrt_integer_value { struct vrt_value parent; int64_t value; }; static struct vrt_value * vrt_integer__new_value(const struct vrt_value_type *type) { struct vrt_integer_value *self= cork_new( struct vrt_integer_value); return &self->parent; } static void vrt_integer__free_value(const struct vrt_value_type *type, struct vrt_value *value) { struct vrt_integer_value *self= cork_container_of(value, struct vrt_integer_value, parent); free(self); } /* The following hash value is produced by the cork-hash utility function */ #define VRT_INTEGER_TYPE 0xcd6e0682 static struct vrt_value_type _vrt_integer_type={ type= VRT_INTEGER_TYPE, vrt_integer__new_value, 1.3. Value objects 5 Varon-T Documentation, Release 2.0.1-dev-5-g376477b vrt_integer__free_value }; const struct vrt_value_type * vrt_integer_type(void) { return &_vrt_integer_type; } The implementation is straightforward and depends on the libcork library. A few details about this implementation are worth mentioning: • The implementation uses embedded C structs to contain or “subclass” the vrt_value type within vrt_integer_value. The disruptor queue library can then operate efficiently on pointers to the contained or “superclass” struct. However, your application will need a pointer to the container struct when given a pointer to the contained struct in order to perform application specific computations. That is the purpose of cork_container_of(). • The _vrt_integer_type does not require additional fields beyond the vrt_value_type interface. Therefore, it is a static value type instance and accessible through vrt_integer_type(). 1.4 Producers A producer is a queue client that feeds values into a disruptor queue. The queue manages the allocation of memory for value instances in the queue, however, so the produces “claims” the next free value instance in the queue. Once claimed, the producer copies or fills in the value instance and “publishes” the value instances, making it available for consumer clients. At the point of publishing the value instance availability, the producer relinquishes its claim to the value instance. The value instance is then considered active and available until all consumer clients inform the disruptor queue they are finished with the value instance. At this point, the disruptor queue makes the value instance’s slot in the queue array available for reuse and claim by a producer. A Varon-T producer client must implement the following interface: struct vrt_producer struct vrt_queue *queue A pointer to the disruptor queue fed by this producer client. unsigned int index The index of this producer client