Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Most efficient method of copying std::deque contents to byte-array

Is there a better way to copy the contents of a std::deque into a byte-array? It seems like there should be something in the STL for doing this.

// Generate byte-array to transmit
uint8_t * i2c_message = new uint8_t[_tx.size()];
if ( !i2c_message ) {
    errno = ENOMEM;
    ::perror("ERROR: FirmataI2c::endTransmission - Failed to allocate memory!");
} else {
    size_t i = 0;

    // Load byte-array
    for ( const auto & data_byte : _tx ) {
        i2c_message[i++] = data_byte;
    }

    // Transmit data
    _marshaller.sendSysex(firmata::I2C_REQUEST, _tx.size(), i2c_message);
    _stream.flush();

    delete[] i2c_message;
}

I'm looking for suggestions for either space or speed or both...

EDIT: It should be noted that _marshaller.sendSysex() cannot throw.

FOLLOW UP:

I thought it would be worth recapping everything, because the comments are pretty illuminating (except for the flame war). :-P

The answer to the question as asked...

Use std::copy

The bigger picture:

Instead of simply increasing the raw performance of the code, it was worth considering adding robustness and longevity to the code base.

I had overlooked RAII - Resource Acquisition is Initialization. By going in the other direction and taking a slight performance hit, I could get big gains in resiliency (as pointed out by @PaulMcKenzie and @WhozCraig). In fact, I could even insulate my code from changes in a dependency!

Final Solution:

In this case, I actually have access to (and the ability to change) the larger code base - often not the case. I reevaluated* the benefit I was gaining from using a std::deque and I swapped the entire underlying container to a std::vector. Thus saving the performance hit of container swapping, and gaining the benefits of contiguous data and RAII.

*I chose a std::deque because I always have to push_front two bytes to finalize my byte-array before sending. However, since it is always two bytes, I was able to pad the vector with two dummy bytes and replace them by random access - O(n) time.

like image 497
Zak Avatar asked Jul 22 '17 23:07

Zak


People also ask

Does Deque take more memory than vector?

Save this answer. Show activity on this post. Deque can have additional memory overhead over vector because it's made of a few blocks instead of contiguous one.

What is std:: memcpy?

std::memcpyCopies count bytes from the object pointed to by src to the object pointed to by dest . Both objects are reinterpreted as arrays of unsigned char. If the objects overlap, the behavior is undefined. If either dest or src is an invalid or null pointer, the behavior is undefined, even if count is zero.

Can you memcpy into a vector?

You can use the Use memcpy for vector assignment parameter to optimize generated code for vector assignments by replacing for loops with memcpy function calls. The memcpy function is more efficient than for -loop controlled element assignment for large data sets. This optimization improves execution speed.


1 Answers

Embrace the C++ standard library. Assuming _tx is really a std::deque<uint8_t>, one way to do this is simply:

std::vector<uint8_t> msg(_tx.cbegin(), _tx.cend());
_marshaller.sendSysex(firmata::I2C_REQUEST, msg.size(), msg.data());

This allocates the proper size contiguous buffer, copies the contents from the source iterator pair, and then invokes your send operation. The vector will be automatically cleaned up on scope-exit, and an exception will be thrown if the allocation for building it somehow fails.

The standard library provides a plethora of ways to toss data around, especially given iterators that mark where to start, and where to stop. Might as well use that to your advantage. Additionally, letting RAII handle the ownership and cleanup of entities like this rather than manual memory management is nearly always a better approach, and should be encouraged.

In all, if you need continuity (and judging by the looks of that send-call, that's exactly why you're doing this), then copying from non-contiguous to contiguous space is pretty much your only option, and that takes space and copy-time. Not much you can do to avoid that. I suppose peeking into the implementation specifics of std::deque and possibly doing something like stacking send-calls would be possible, but I seriously doubt there would be any reward, and the only savings would likely evaporate in the multi-send invokes.

Finally, there is another option that may be worth considering. Look at the source of all of this. Is a std::deque really warranted? For example, certainly your building that container somewhere else. If you can do that build operation as-efficient, or nearly-so, using a std::vector, then this entire problem goes away, as you can just send that.

For example, if you knew (provably) that your std::deque would never be larger than some size N, you could, pre-size a std::vector or similar continuous RAII-protected allocation, to be 2*N in size, start both a fore and aft iterator pair in the middle and either prepend data by walking the fore iterator backward, or append data by walking the aft iterator forward. In the end, your data will be contiguous between fore and aft, and the send is all that remains. no copies would be required, though added-space is still required. This all hinges on knowing with certainty the maximum message size. If that is available to you, it may be an idea worth profiling.

like image 159
WhozCraig Avatar answered Sep 22 '22 12:09

WhozCraig