TBufferedTransport and TBufferBase at the TTransport layer of thrift

Source: Internet
Author: User

This section describes the buffer-related transmission classes. cache is used to improve read/write efficiency. Thrift first establishes a cache base class when implementing cache transmission. Then, classes that need to implement the cache function can be directly inherited from this base class. This base class and a specific implementation class are analyzed in detail below.
Cache base class TBufferBase
The cache base class enables all read/write functions of the transmission class to provide cache to improve performance. Generally, memcpy is used to design and implement fast-path read/write operations. These operation functions are usually small, non-virtual, and inline functions. TBufferBase is an abstract base class. sub-classes must implement operations such as slow-path read/write functions. Slow-path read/write operations are mainly performed when the cache is full or empty. First, let's take a look at the definition of the cache base class. The Code is as follows:
[Cpp]
Class TBufferBase: public TVirtualTransport <TBufferBase> {
Public:
Uint32_t read (uint8_t * buf, uint32_t len) {// read function
Uint8_t * new_rBase = rBase _ + len; // obtain the cache boundary to be read.
If (TDB_LIKELY (new_rBase <= rBound _) {// determines whether the cache has sufficient data to be readable, using the Branch Prediction Technology
Std: memcpy (buf, rBase _, len); // direct memory copy
RBase _ = new_rBase; // update the cache read base address
Return len; // return the read Length
}
Return readSlow (buf, len); // If the cache does not meet the read length requirement, perform Slow read.
}
Uint32_t readAll (uint8_t * buf, uint32_t len ){
Uint8_t * new_rBase = rBase _ + len; // same read function
If (TDB_LIKELY (new_rBase <= rBound _)){
Std: memcpy (buf, rBase _, len );
RBase _ = new_rBase;
Return len;
}
Return apache: thrift: transport: readAll (* this, buf, len); // call
}
Void write (const uint8_t * buf, uint32_t len) {// fast write function
Uint8_t * new_wBase = wBase _ + len; // The new cache base address after writing
If (TDB_LIKELY (new_wBase <= wBound _) {// determine whether the cache has sufficient space to write data.
Std: memcpy (wBase _, buf, len); // memory copy
WBase _ = new_wBase; // update the base address
Return;
}
WriteSlow (buf, len); // If the cache space is insufficient, the slow write function is called.
}
Const uint8_t * borrow (uint8_t * buf, uint32_t * len) {// fast path borrow
If (TDB_LIKELY (static_cast <ptrdiff_t> (* len) <= rBound _-rBase _) {// determine whether the borrow length is sufficient
* Len = static_cast <uint32_t> (rBound _-rBase _);
Return rBase _; // return the borrowed base address
}
Return borrowSlow (buf, len); // if it is insufficient, use the slow path to borrow
}
Void consume (uint32_t len) {// Consumption Function
If (TDB_LIKELY (static_cast <ptrdiff_t> (len) <= rBound _-rBase _) {// determine whether the cache is sufficient for consumption
RBase _ + = len; // update the consumed Length
} Else {
Throw TTransportException (TTransportException: BAD_ARGS,
"Consume did not follow a borrow."); // throw an exception
}
}
Protected:
Virtual uint32_t readSlow (uint8_t * buf, uint32_t len) = 0; // slow Function
Virtual void writeSlow (const uint8_t * buf, uint32_t len) = 0;
Virtual const uint8_t * borrowSlow (uint8_t * buf, uint32_t * len) = 0;
TBufferBase ()
: RBase _ (NULL)
, RBound _ (NULL)
, WBase _ (NULL)
, WBound _ (NULL)
{} // Constructor, set all cache spaces to NULL
Void setReadBuffer (uint8_t * buf, uint32_t len) {// set the read cache space address
RBase _ = buf; // read cache start address
RBound _ = buf + len; // read cache address limit
}
Void setWriteBuffer (uint8_t * buf, uint32_t len) {// set the write cache address space
WBase _ = buf; // start
WBound _ = buf + len; // Boundary
}
Virtual ~ TBufferBase (){}
Uint8_t * rBase _; // read from here
Uint8_t * rBound _; // read limit
Uint8_t * wBase _; // Write Start address
Uint8_t * wBound _; // write Boundary
};

Class TBufferBase: public TVirtualTransport <TBufferBase> {
Public:
Uint32_t read (uint8_t * buf, uint32_t len) {// read function
Uint8_t * new_rBase = rBase _ + len; // obtain the cache boundary to be read.
If (TDB_LIKELY (new_rBase <= rBound _) {// determines whether the cache has sufficient data to be readable, using the Branch Prediction Technology
Std: memcpy (buf, rBase _, len); // direct memory copy
RBase _ = new_rBase; // update the cache read base address
Return len; // return the read Length
}
Return readSlow (buf, len); // If the cache does not meet the read length requirement, perform Slow read.
}
Uint32_t readAll (uint8_t * buf, uint32_t len ){
Uint8_t * new_rBase = rBase _ + len; // same read function
If (TDB_LIKELY (new_rBase <= rBound _)){
Std: memcpy (buf, rBase _, len );
RBase _ = new_rBase;
Return len;
}
Return apache: thrift: transport: readAll (* this, buf, len); // call
}
Void write (const uint8_t * buf, uint32_t len) {// fast write function
Uint8_t * new_wBase = wBase _ + len; // The new cache base address after writing
If (TDB_LIKELY (new_wBase <= wBound _) {// determine whether the cache has sufficient space to write data.
Std: memcpy (wBase _, buf, len); // memory copy
WBase _ = new_wBase; // update the base address
Return;
}
WriteSlow (buf, len); // If the cache space is insufficient, the slow write function is called.
}
Const uint8_t * borrow (uint8_t * buf, uint32_t * len) {// fast path borrow
If (TDB_LIKELY (static_cast <ptrdiff_t> (* len) <= rBound _-rBase _) {// determine whether the borrow length is sufficient
* Len = static_cast <uint32_t> (rBound _-rBase _);
Return rBase _; // return the borrowed base address
}
Return borrowSlow (buf, len); // if it is insufficient, use the slow path to borrow
}
Void consume (uint32_t len) {// Consumption Function
If (TDB_LIKELY (static_cast <ptrdiff_t> (len) <= rBound _-rBase _) {// determine whether the cache is sufficient for consumption
RBase _ + = len; // update the consumed Length
} Else {
Throw TTransportException (TTransportException: BAD_ARGS,
"Consume did not follow a borrow."); // throw an exception
}
}
Protected:
Virtual uint32_t readSlow (uint8_t * buf, uint32_t len) = 0; // slow Function
Virtual void writeSlow (const uint8_t * buf, uint32_t len) = 0;
Virtual const uint8_t * borrowSlow (uint8_t * buf, uint32_t * len) = 0;
TBufferBase ()
: RBase _ (NULL)
, RBound _ (NULL)
, WBase _ (NULL)
, WBound _ (NULL)
{} // Constructor, set all cache spaces to NULL
Void setReadBuffer (uint8_t * buf, uint32_t len) {// set the read cache space address
RBase _ = buf; // read cache start address
RBound _ = buf + len; // read cache address limit
}
Void setWriteBuffer (uint8_t * buf, uint32_t len) {// set the write cache address space
WBase _ = buf; // start
WBound _ = buf + len; // Boundary
}
Virtual ~ TBufferBase (){}
Uint8_t * rBase _; // read from here
Uint8_t * rBound _; // read limit
Uint8_t * wBase _; // Write Start address
Uint8_t * wBound _; // write Boundary
}; From the definition of TBufferBase, it can be seen that it also inherits from the virtual class, mainly using the memcpy function to achieve fast cache reading, when determining whether there is sufficient cache space for operations, branch prediction technology is used to provide code execution efficiency, and all the fast-path functions are non-virtual, inline small-code functions. Next let's take a look at a specific implementation of a subclass of the cache base class!
TBufferedTransport
The cache transmission class is inherited from the cache base class. For read, the actual size of read data is much larger than the actual size of the request, and the excess data will be used for data that exceeds the local cache in the future; for write: data will be written to the memory cache before it is sent.
The default cache size is 512 bytes (Code: static const int DEFAULT_BUFFER_SIZE = 512;). Multiple constructor functions are provided to specify only one transmission class (another level) or specify the size of the read/write cache. Because it is a cache class that can be actually used, you need to implement Slow read and write functions. It also enables open function, close function close, and refresh function flush to determine whether data is in the pending state. The function peek is defined and implemented as follows:
[Cpp]
Bool peek (){
If (rBase _ = rBound _) {// check whether the read base address and read boundary overlap, that is, the read has been completed.
SetReadBuffer (rBuf _. get (), transport _-> read (rBuf _. get (), rBufSize _); // yes: re-read data from the underlying layer
}
Return (rBound _> rBase _); // If the boundary is greater than the base address, there are pending status data.
}
Next let's take a look at the implementation details of slow-read and slow-Write Functions (fast-read and fast-write inherit the base class: that is, the default read/write operations are directly read from the cache, the so-called fast-read and fast-write ). The implementation of the slow-read function is as follows (note in detail ):
Uint32_t TBufferedTransport: readSlow (uint8_t * buf, uint32_t len ){
Uint32_t have = rBound _-rBase _; // calculate the amount of data in the cache.

// If the existing data in the cache cannot meet our requirements,
// We (in this case only) should read data from the slow path.
Assert (have <len );

// If we have some data in the cache, copy it and return it
// We have to return it and try to read more data, because we cannot guarantee
// The lower-layer transmission actually has more data, so it tries to read it in blocking mode.
If (have> 0 ){
Memcpy (buf, rBase _, have); // copy data
SetReadBuffer (rBuf _. get (), 0); // sets the read cache. The base class implements this function.
Return have; // return the incomplete data that already exists in the cache.
}

// No more data is available in our cache. More data is transmitted from the lower layer to reach the buffer size.
// Note that if len is smaller than rBufSize _, multiple scenarios may occur. Otherwise, it will be meaningless.
SetReadBuffer (rBuf _. get (), transport _-> read (rBuf _. get (), rBufSize _); // read data and set read Cache

// Process existing data
Uint32_t give = std: min (len, static_cast <uint32_t> (rBound _-rBase _));
Memcpy (buf, rBase _, give );
RBase _ + = give;

Return give;
}

Bool peek (){
If (rBase _ = rBound _) {// check whether the read base address and read boundary overlap, that is, the read has been completed.
SetReadBuffer (rBuf _. get (), transport _-> read (rBuf _. get (), rBufSize _); // yes: re-read data from the underlying layer
}
Return (rBound _> rBase _); // If the boundary is greater than the base address, there are pending status data.
}
Next let's take a look at the implementation details of slow-read and slow-Write Functions (fast-read and fast-write inherit the base class: that is, the default read/write operations are directly read from the cache, the so-called fast-read and fast-write ). The implementation of the slow-read function is as follows (note in detail ):
Uint32_t TBufferedTransport: readSlow (uint8_t * buf, uint32_t len ){
Uint32_t have = rBound _-rBase _; // calculate the amount of data in the cache.
  
// If the existing data in the cache cannot meet our requirements,
// We (in this case only) should read data from the slow path.
Assert (have <len );
  
// If we have some data in the cache, copy it and return it
// We have to return it and try to read more data, because we cannot guarantee
// The lower-layer transmission actually has more data, so it tries to read it in blocking mode.
If (have> 0 ){
Memcpy (buf, rBase _, have); // copy data
SetReadBuffer (rBuf _. get (), 0); // sets the read cache. The base class implements this function.
Return have; // return the incomplete data that already exists in the cache.
}
  
// No more data is available in our cache. More data is transmitted from the lower layer to reach the buffer size.
// Note that if len is smaller than rBufSize _, multiple scenarios may occur. Otherwise, it will be meaningless.
SetReadBuffer (rBuf _. get (), transport _-> read (rBuf _. get (), rBufSize _); // read data and set read Cache
  
// Process existing data
Uint32_t give = std: min (len, static_cast <uint32_t> (rBound _-rBase _));
Memcpy (buf, rBase _, give );
RBase _ + = give;
  
Return give;
} The main consideration of slow-reading functions is that there is still some data in the cache, but it is not long enough for us to read. It is also troublesome that although there is no data in the cache, however, when we transmit data from the lower layer, the read length may be greater than, less than, or equal to the length we need to read, so we need to consider various situations. Next, we will continue to analyze the implementation details of the slow write function:
[Cpp]
Void TBufferedTransport: writeSlow (const uint8_t * buf, uint32_t len ){
Uint32_t have_bytes = wBase _-wBuf _. get (); // calculate the number of bytes in the write Cache
Uint32_t space = wBound _-wBase _; // calculate the remaining write cache space
// If the free space in the cache area cannot accommodate our data, we adopt slow path writing (only)
Assert (wBound _-wBase _ <static_cast <ptrdiff_t> (len ));

// Check whether the existing data plus the data to be written is greater than twice the write cache or the cache is empty.
If (have_bytes + len> = 2 * wBufSize _) | (have_bytes = 0 )){
If (have_bytes> 0) {// The cache is greater than 0 and the length of the data to be written is greater than twice that of the cache.
Transport _-> write (wBuf _. get (), have_bytes); // write existing data to the lower-layer transmission first
}
Transport _-> write (buf, len); // write The len Length data.
WBase _ = wBuf _. get (); // obtain the base address of the write cache again.
Return;
}

Memcpy (wBase _, buf, space); // fill our internal cache to write
Buf + = space;
Len-= space;
Transport _-> write (wBuf _. get (), wBufSize _); // write to lower-layer transmission

Assert (len <wBufSize _);
Memcpy (wBuf _. get (), buf, len); // copy the remaining data to our cache
WBase _ = wBuf _. get () + len; // obtain the write cache base address again.
Return;
}

Void TBufferedTransport: writeSlow (const uint8_t * buf, uint32_t len ){
Uint32_t have_bytes = wBase _-wBuf _. get (); // calculate the number of bytes in the write Cache
Uint32_t space = wBound _-wBase _; // calculate the remaining write cache space
// If the free space in the cache area cannot accommodate our data, we adopt slow path writing (only)
Assert (wBound _-wBase _ <static_cast <ptrdiff_t> (len ));
  
// Check whether the existing data plus the data to be written is greater than twice the write cache or the cache is empty.
If (have_bytes + len> = 2 * wBufSize _) | (have_bytes = 0 )){
If (have_bytes> 0) {// The cache is greater than 0 and the length of the data to be written is greater than twice that of the cache.
Transport _-> write (wBuf _. get (), have_bytes); // write existing data to the lower-layer transmission first
}
Transport _-> write (buf, len); // write The len Length data.
WBase _ = wBuf _. get (); // obtain the base address of the write cache again.
Return;
}
  
Memcpy (wBase _, buf, space); // fill our internal cache to write
Buf + = space;
Len-= space;
Transport _-> write (wBuf _. get (), wBufSize _); // write to lower-layer transmission
  
Assert (len <wBufSize _);
Memcpy (wBuf _. get (), buf, len); // copy the remaining data to our cache
WBase _ = wBuf _. get () + len; // obtain the write cache base address again.
Return;
} Slow function writing is also tricky, that is, we should copy our data to our internal cache and send it from there, or we should simply use a system call to write the content in the current internal write cache area, and then use a system call to write the data that needs to be written into len again. If the data in the current cache area plus the data we need to write this time is at least twice the length of our cache area, we will have to call the system call at least twice (unless the cache is empty), so we will not copy it. Otherwise, we will increase the number in sequence. The specific implementation of situation-based processing. Finally, let's look at the implementation of slow borrow functions. The main purpose of this function is to implement variable length encoding. The Implementation Details of the slow borrow function are as follows:
[Cpp]
Const uint8_t * TBufferedTransport: borrowSlow (uint8_t * buf, uint32_t * len ){
(Void) buf;
(Void) len;
Return NULL; // NULL is returned by default.
}

Const uint8_t * TBufferedTransport: borrowSlow (uint8_t * buf, uint32_t * len ){
(Void) buf;
(Void) len;
Return NULL; // NULL is returned by default.
}

In this class, we can see that it does not do anything, but simply returns NULL, so it needs to block lending. According to the official statement, the following two actions should be implemented in the current version and may change in future versions:
If the maximum length to be lent is the length of the cache area, NULL is never returned. Depending on the underlying transmission, it should throw an exception or never stop;
Some borrow requests may copy internal bytes. If the borrow length is at most half of the cache area, do not copy it internally. Save this restriction to optimize performance.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.