Помимо ответа от stribor14 у меня есть еще два предложения. Они основаны только на производительности, поэтому читабельный или обслуживаемый код здесь не будет. Когда все места будут записаны, вторая половина будет скопирована в первую. Мой первый инстинкт подсказывает, что это могло бы быть лучше. Мое рассуждение заключалось в том, что общее количество записей будет одинаковым, но все записи будут последовательными (вместо того, чтобы перескакивать каждую секунду записи в другое место в массиве).
#include <cstddef>
#include <cstring>
#include <array>
const size_t buffer_size = 50'000;
int main()
{
std::array<double, 2 * buffer_size> buffer{};
double *index = buffer.data();
double *mid = index + buffer_size;
for (double data = 0.0; data < 10 * buffer_size; data += 1.0)
{
if (index == mid)
{
index = buffer.data();
std::memcpy(index, mid, buffer_size * sizeof(double));
}
*(index++ + buffer_size) = data;
foo(index, buffer_size);
}
}
В качестве альтернативы я думал, что это возможно чтобы оптимизировать собственный ответ OP, чтобы удалить доступ к массиву. Идея состоит в том, что buffer[buffer_idx - buffer_size]
требует 2 сложения для вычисления местоположения этого значения, а именно: *(buffer + buffer_idx - buffer_size)
. Если buffer_idx
содержит указатель, требуется только одно добавление. Это дает следующий код:
#include <cstddef>
#include <array>
const size_t buffer_size = 50'000;
int main()
{
std::array<double, buffer_size * 2> buffer{};
double *index = buffer.data();
double *mid = buffer.data() + buffer_size;
for (double data = 0.0; data < 10 * buffer_size; data += 1.0)
{
*index = data;
*(index + buffer_size) = data;
++index;
index -= buffer_size * (index == mid);
foo(index, buffer_size);
}
}
Теперь я заметил, что иду по кроличьей норе оптимизации C ++. Так что мы не могли останавливаться на достигнутом. Чтобы выбрать, какую реализацию использовать, я хотел запустить тест. Вернер Пиркл дал хорошую отправную точку . Но запускать это в нашем оптимизированном коде бессмысленно, потому что измеренное время составляет 0 мкс. Так что давайте немного изменим его. Я написал al oop more внутри теста, чтобы дать ему время выполнения, и придумал:
const int repeats = 1000;
volatile double *ptr;
int duration = 0;
const size_t buffer_size = 50'000;
// ... Set up of the buffers and indices
for (int i = 0; i < repeats; ++i)
{
auto t1 = std::chrono::high_resolution_clock::now();
for (double data = 0.0; data < 10 * buffer_size; data += 1.0)
{
// ... add data to circular buffer
ptr = // ... the start of the array
}
auto t2 = std::chrono::high_resolution_clock::now();
duration += std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
}
(обратите внимание на использование volatile double *
, чтобы гарантировать, что необработанный указатель в непрерывный массив не оптимизирован.)
Во время выполнения этих тестов я заметил, что они очень зависят от флагов компилятора (-O2 -O3 -march = native ...). Я приведу некоторые результаты, но, как и все тесты C ++, относитесь к ним с недоверием и выполняйте свои собственные с реальной рабочей нагрузкой. (Время указано в среднем нс на вставку)
with `memcpy` stribor14 `operator[]` with pointers
|---------------|-----------|--------------|---------------|
-O2 | 1.38 | 1.57 | 1.41 | 1.15 |
-O3 | 1.37 | 1.63 | 1.36 | 1.09 |
-O3 -march=native | 1.35 | 1.61 | 1.34 | 1.09 |
Излишне говорить: я был весьма разочарован тем, что, по моему мнению, должно работать лучше всего. Но, как указывалось ранее, этот тест никоим образом не отражает реальную производительность.