arduino / arduino/ArduinoCore-renesas

Performance of SafeRingBufferN - (Used in the UART class)

Open
#75 1 comment 0 reactions 0 assignees View on GitHub
topic: code type: enhancement
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
![image](https://github.com/arduino/ArduinoCore-renesas/assets/1558080/77df7697-9bb9-4959-8220-538fe2e50892)
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.