arduino / arduino/ArduinoCore-renesas
Performance of SafeRingBufferN - (Used in the UART class)
- Dominant language
- C
- Stars
- 193
- Forks
- 112
- PR merge metrics
- No merged PRs in 30d
Description
Note: earlier today I documented more of this up on the forum thread:
https://forum.arduino.cc/t/performance-of-saferingbuffern/1152977
The WIP code is up at: https://github.com/KurtE/ArduinoCore-renesas/tree/SerialX_use_write_fifo
I am experimenting with implementing the use of the RingBuffer in the Serial Transmit code, and noticed that my first cut of the code was spending a lot more time in the write method than I was expecting. There are things I can do to speed this up, like in this actual case bypass using the Ring buffer.
The beginning part of the write code:
```
Size_t UART::write(const uint8_t* c, size_t len) {
if(init_ok && (len > 0)) {
DBGDigitalWrite(A5, HIGH);
size_t cb_left = len;
while (cb_left) {
// see if we have room in the FIFO to store this character.
bool tx_active_on_entry = tx_fsi_active;
// Put as much of data into txBuffer as will fit
while(cb_left && !txBuffer.isFull()) {
txBuffer.store_char(*c++);
cb_left--;
}
// if the UART was not active at the start we will start it.
if (!tx_active_on_entry) {
size_t cb = 0;
while (cb < sizeof(tx_fsi_buffer)) {
int ch = txBuffer.read_char();
if (ch == -1) break;
tx_fsi_buffer[cb++] = ch;
}
tx_fsi_active = true;
DBGDigitalToggle(A2);
R_SCI_UART_Write(&(uart_ctrl), tx_fsi_buffer, cb);
,,,
```
In the case below I could detect that the queue is empty and transmit is not active and transfer directly to a temporary buffer that I pass off to R_SCI_UART_Write,
But in my first test case I was trying, which is to control Robotis servos at 1mbs half duplex. I see the timings of sending a logical packet

Channel 6 shows the time while we are in the write method. Channel 7 I do a toggle of the pin when I actually call out to the R_SCI_UART_Write.
It is taking about 50us in the write method when using the SafeRingBuffer. I did a quck and dirty check to see how long it would take if I used the RingBufferN instead, and the time was cut to about 17us.
I was hoping that the straight RingBufferN would be safe enough to use for the typical case of a single producer of data, and a single consumer of it. But I don't believe your implementation of this class is safe enough.
As the two methods store_char and read_char both modify a common member variable _numElems.
```
void RingBufferN::store_char( uint8_t c )
{
// if we should be storing the received character into the location
// just before the tail (meaning that the head would advance to the
// current location of the tail), we're about to overflow the buffer
// and so we don't write the character or advance the head.
if (!isFull())
{
_aucBuffer[_iHead] = c ;
_iHead = nextIndex(_iHead);
_numElems++;
}
}
template
int RingBufferN::read_char()
{
if (isEmpty())
return -1;
uint8_t value = _aucBuffer[_iTail];
_iTail = nextIndex(_iTail);
_numElems--;
return value;
}
```
In other implementations of like code, the store_char would only modify the _iHead member variable and the read_char would only modify the _iTail member.
When you need information like Available, you can easily compute it, something like:
```
template
int RingBufferN::available()
{
uint32_t head = _iHead;
uint32_t tail = _iTail;
if (head >= tail) return (head - tail);
return = N + head - tail;
}
```
typed on fly...
There are more details up on the forum thread including the example sketch I was running. And more details on my attempt to describe the code base to another forum member.
**EDIT:** I am not sure if it was clear above, but the differences between the SafeRingBufferN and the simple RingBufferNm is the methods like store_char use the Synchronized code like:
```
template
void SafeRingBufferN::store_char(uint8_t c) {
synchronized {
RingBufferN::store_char(c);
}
}
```
use the synchronized loop around code, which creates an __Guard object on entry, who disables all interrupt on creation, and restores the global interrupt enable state on exit (i.e., when the code leaves the scope).
Thoughts?
Contributor guide
No contributing guide indexed for this repository
Research direction
Read the forum thread and the SerialX_use_write_fifo branch, then inspect UART::write, SafeRingBufferN::store_char, SafeRingBufferN::read_char, and the corresponding RingBufferN methods. Determine the intended single-producer/single-consumer safety and performance requirements; done means the issue has a decided approach, validated timing or concurrency behavior, and any necessary tests or documentation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- embedded-iot, performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100