У меня есть два массива, и я хочу скопировать один массив в другой с некоторым шагом. Например, у меня есть
A A A A A A A A ...
B B B B B B B B ...
и я хочу скопировать каждые три элемента B
в A
чтобы получить
B A A B A A B A ...
Из поста «Существует ли стандартная стандартная версия memcpy?«Похоже, что у C. такой возможности нет
Тем не менее, я испытал это, в некоторых случаях, memcpy
быстрее, чем for
копия на основе цикла.
Мой вопрос Есть ли какой-нибудь способ эффективно выполнить пошаговую копию памяти в C ++, выполняя хотя бы стандарт for
петля?
Большое спасибо.
РЕДАКТИРОВАТЬ — УТОЧНЕНИЕ ПРОБЛЕМЫ
Чтобы прояснить проблему, давайте обозначим эти два массива под a
а также b
, У меня есть функция, которая выполняет уникальное следующее for
петля
for (int i=0; i<NumElements, i++)
a_[i] = b_[i];
где оба []
Это перегруженные операторы (я использую технику шаблонов выражений), так что они могут иметь в виду, например,
a[3*i]=b[i];
Есть ли какой-нибудь способ эффективно выполнить пошаговое копирование памяти в C ++, выполняя хотя бы стандарт для цикла?
Изменить 2: В библиотеках C ++ нет функции для пошагового копирования.
Поскольку пошаговое копирование не так популярно при копировании в память, производители микросхем и языковые дизайны имеют специализированную поддержку для пошагового копирования.
Принимая стандарт for
цикл, вы можете получить некоторую производительность с помощью Разматывание петли. Некоторые компиляторы имеют опции для развертывания циклов; это не «стандартный» вариант.
Учитывая стандарт for
цикл:
#define RESULT_SIZE 72
#define SIZE_A 48
#define SIZE_B 24
unsigned int A[SIZE_A];
unsigned int B[SIZE_B];
unsigned int result[RESULT_SIZE];
unsigned int index_a = 0;
unsigned int index_b = 0;
unsigned int index_result = 0;
for (index_result = 0; index_result < RESULT_SIZE;)
{
result[index_result++] = B[index_b++];
result[index_result++] = A[index_a++];
result[index_result++] = A[index_a++];
}
Развертывание цикла повторяло бы содержимое «стандарта» for
цикл:
for (index_result = 0; index_result < RESULT_SIZE;)
{
result[index_result++] = B[index_b++];
result[index_result++] = A[index_a++];
result[index_result++] = A[index_a++];
result[index_result++] = B[index_b++];
result[index_result++] = A[index_a++];
result[index_result++] = A[index_a++];
}
в развернутый версия, количество петель было сокращено в два раза.
Улучшение производительности может быть незначительным по сравнению с другими вариантами.
Следующие проблемы влияют на производительность, и каждый может иметь различные улучшения скорости:
Один пример параллельной обработки состоит в том, чтобы один процессор скопировал элементы B в новый массив, а другой процессор скопировал элементы A в новый массив.
Это может быть слишком конкретный ответ, но на платформе ARM, которая поддерживает NEON, векторизация NEON может использоваться для еще более быстрого копирования. Это может быть спасением жизни в среде, где ресурсы относительно более ограничены, и, вероятно, именно поэтому ARM используется в этих условиях в первую очередь. Ярким примером является Android, где большинство устройств все еще используют архитектуру ARM v7a, которая поддерживает NEON.
Следующие примеры демонстрируют это, это цикл для копирования полуплоскостной плоскости UV изображения YUV420sp в плоскую плоскость UV изображения YUV420p. Размеры исходного и целевого буферов оба 640*480/2
байт. Все примеры скомпилированы с g ++ 4.8 внутри Android NDK r9d. Они выполнены на процессоре Samsung Exynos Octa 5420:
1-й уровень: регулярное
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
for(int i=0;i<stride;i++){
dstptr[i] = srcptr[i*2];
dstptr[i + stride] = srcptr[i*2 + 1];
}
}
Составлено с -O3
только занимает около 1,5 мс в среднем.
Уровень 2: Развернул и сжал немного больше с помощью движущихся указателей
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
unsigned char* endptr = dstptr + stride;
while(dstptr<endptr){
*(dstptr + 0) = *(srcptr + 0);
*(dstptr + stride + 0) = *(srcptr + 1);
*(dstptr + 1) = *(srcptr + 2);
*(dstptr + stride + 1) = *(srcptr + 3);
*(dstptr + 2) = *(srcptr + 4);
*(dstptr + stride + 2) = *(srcptr + 5);
*(dstptr + 3) = *(srcptr + 6);
*(dstptr + stride + 3) = *(srcptr + 7);
*(dstptr + 4) = *(srcptr + 8);
*(dstptr + stride + 4) = *(srcptr + 9);
*(dstptr + 5) = *(srcptr + 10);
*(dstptr + stride + 5) = *(srcptr + 11);
*(dstptr + 6) = *(srcptr + 12);
*(dstptr + stride + 6) = *(srcptr + 13);
*(dstptr + 7) = *(srcptr + 14);
*(dstptr + stride + 7) = *(srcptr + 15);
srcptr+=16;
dstptr+=8;
}
}
Составлено с -O3
только занимает около 1,15 мс в среднем. Это, вероятно, так же быстро, как это происходит на обычной архитектуре, как в другом ответе.
Уровень 3: Обычная + GCC автоматическая NEON векторизация
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
for(int i=0;i<stride;i++){
dstptr[i] = srcptr[i*2];
dstptr[i + stride] = srcptr[i*2 + 1];
}
}
Составлено с -O3 -mfpu=neon -ftree-vectorize -ftree-vectorizer-verbose=1 -mfloat-abi=softfp
, занимает в среднем около 0,6 мс. Для справки, memcpy
из 640*480
байт, или удвоенное количество тестируемого здесь, занимает в среднем около 0,6 мс.
Как примечание, второй код (развернутый и указанный), скомпилированный с параметрами NEON выше, занимает примерно то же самое время, 0,6 мс.