Подтвердить что ты не робот

Более быстрая альтернатива memcpy?

У меня есть функция, которая выполняет memcpy, но она занимает огромное количество циклов. Есть ли более быстрая альтернатива/подход, чем использование memcpy для перемещения части памяти?

4b9b3361

Ответ 1

memcpy скорее всего будет самым быстрым способом копирования байтов в памяти. Если вам нужно что-то быстрее - попробуйте выяснить способ не копировать вещи, например. указатели swap, а не сами данные.

Ответ 2

Пожалуйста, предложите нам более подробную информацию. В i386-архитектуре очень возможно, что memcpy является самым быстрым способом копирования. Но в другой архитектуре, для которой у компилятора нет оптимизированной версии, лучше всего переписать функцию memcpy. Я сделал это на пользовательской архитектуре ARM, используя язык ассемблера. Если вы передаете БОЛЬШИЕ куски памяти, то DMA, вероятно, ответ, который вы ищете.

Пожалуйста, предложите более подробную информацию - архитектура, операционная система (если необходимо).

Ответ 3

Обычно стандартная библиотека, поставляемая вместе с компилятором, будет реализовывать memcpy() самый быстрый способ для целевой платформы уже.

Ответ 4

Собственно, memcpy НЕ является самым быстрым способом, особенно если вы его вызываете много раз. У меня также был код, который мне действительно нужно было ускорить, а memcpy медленный, потому что у него слишком много ненужных проверок. Например, он проверяет, перекрываются ли блоки памяти адресата и источника, и если он должен начать копирование с задней части блока, а не спереди. Если вас не волнуют такие соображения, вы, безусловно, можете сделать значительно лучше. У меня есть код, но вот, возможно, еще лучшая версия:

Очень быстро memcpy для обработки изображений?.

Если вы выполняете поиск, вы можете найти и другие реализации. Но для истинной скорости вам нужна версия сборки.

Ответ 6

Это ответ для x86_64 с установленным набором команд AVX2. Хотя что-то подобное может применяться для ARM/AArch64 с SIMD.

В Ryzen 1800X с полным каналом с полной памятью (2 слота, 16 ГБ DDR4 в каждом) следующий код в 1,56 раза быстрее, чем memcpy() в компиляторе MSVС++ 2017. Если вы заполняете оба канала памяти двумя модулями DDR4, т.е. У вас есть все 4 слота DDR4, вы можете получить еще 2 раза быстрее копирования памяти. Для систем с тремя/четырьмя канальными памятью вы можете получить еще 1,5 (2,0) раза быстрее копирование памяти, если код расширен до аналогичного кода AVX512. При использовании только трехканальных/четырехканальных систем с поддержкой AVX2 со всеми занятыми слотами ожидается, что они будут быстрее, потому что для их полной загрузки вам необходимо загрузить или сохранить более 32 байт одновременно (48 байтов для трех- и 64-байтных для четырехканального системы), в то время как AVX2 может одновременно загружать/хранить не более 32 байт. Хотя многопоточность на некоторых системах может облегчить это без AVX512 или даже с AVX2.

Итак, вот код копирования, предполагающий, что вы копируете большой блок памяти, размер которого кратен 32, а блок 32-байтовый.

Для блоков с несколькими размерами и не выровненными могут быть записаны код пролога/эпилога, уменьшающий ширину до 16 (SSE4.1), 8, 4, 2 и, наконец, 1 байт сразу для головки блока и хвоста. Также в середине локальный массив из 2-3 значений __m256i может использоваться как прокси-сервер между выровненными чтениями из источника и выровненной записи в пункт назначения.

#include <immintrin.h>
#include <cstdint>
/* ... */
void fastMemcpy(void *pvDest, void *pvSrc, size_t nBytes) {
  assert(nBytes % 32 == 0);
  assert((intptr_t(pvDest) & 31) == 0);
  assert((intptr_t(pvSrc) & 31) == 0);
  const __m256i *pSrc = reinterpret_cast<const __m256i*>(pvSrc);
  __m256i *pDest = reinterpret_cast<__m256i*>(pvDest);
  int64_t nVects = nBytes / sizeof(*pSrc);
  for (; nVects > 0; nVects--, pSrc++, pDest++) {
    const __m256i loaded = _mm256_stream_load_si256(pSrc);
    _mm256_stream_si256(pDest, loaded);
  }
  _mm_sfence();
}

Ключевой особенностью этого кода является то, что он пропускает кеш процессора при копировании: когда задействуется процессорный кеш (то есть инструкции AVX без _stream_), скорость копирования падает несколько раз в моей системе.

Моя память DDR4 составляет 2.6 ГГц CL13. Поэтому при копировании 8 ГБ данных из одного массива в другой я получил следующие скорости:

memcpy(): 17 208 004 271 bytes/sec.
Stream copy: 26 842 874 528 bytes/sec.

Обратите внимание, что в этих измерениях общий размер обоих входных и выходных буферов делится на количество прошедших секунд. Потому что для каждого байта массива есть 2 обращения к памяти: один для чтения байта из входного массива, другой для записи байта в выходной массив. Другими словами, при копировании 8 ГБ из одного массива в другой вы выполняете операции доступа к памяти на 16 ГБ.

Умеренная многопоточность может дополнительно повысить производительность около 1,44 раза, поэтому общее увеличение по сравнению с memcpy() достигает 2,55 раз на моей машине. Здесь, как скорость копирования потока зависит от количества потоков, используемых на моей машине:

Stream copy 1 threads: 27114820909.821 bytes/sec
Stream copy 2 threads: 37093291383.193 bytes/sec
Stream copy 3 threads: 39133652655.437 bytes/sec
Stream copy 4 threads: 39087442742.603 bytes/sec
Stream copy 5 threads: 39184708231.360 bytes/sec
Stream copy 6 threads: 38294071248.022 bytes/sec
Stream copy 7 threads: 38015877356.925 bytes/sec
Stream copy 8 threads: 38049387471.070 bytes/sec
Stream copy 9 threads: 38044753158.979 bytes/sec
Stream copy 10 threads: 37261031309.915 bytes/sec
Stream copy 11 threads: 35868511432.914 bytes/sec
Stream copy 12 threads: 36124795895.452 bytes/sec
Stream copy 13 threads: 36321153287.851 bytes/sec
Stream copy 14 threads: 36211294266.431 bytes/sec
Stream copy 15 threads: 35032645421.251 bytes/sec
Stream copy 16 threads: 33590712593.876 bytes/sec

Код:

void AsyncStreamCopy(__m256i *pDest, const __m256i *pSrc, int64_t nVects) {
  for (; nVects > 0; nVects--, pSrc++, pDest++) {
    const __m256i loaded = _mm256_stream_load_si256(pSrc);
    _mm256_stream_si256(pDest, loaded);
  }
}

void BenchmarkMultithreadStreamCopy(double *gpdOutput, const double *gpdInput, const int64_t cnDoubles) {
  assert((cnDoubles * sizeof(double)) % sizeof(__m256i) == 0);
  const uint32_t maxThreads = std::thread::hardware_concurrency();
  std::vector<std::thread> thrs;
  thrs.reserve(maxThreads + 1);

  const __m256i *pSrc = reinterpret_cast<const __m256i*>(gpdInput);
  __m256i *pDest = reinterpret_cast<__m256i*>(gpdOutput);
  const int64_t nVects = cnDoubles * sizeof(*gpdInput) / sizeof(*pSrc);

  for (uint32_t nThreads = 1; nThreads <= maxThreads; nThreads++) {
    auto start = std::chrono::high_resolution_clock::now();
    lldiv_t perWorker = div((long long)nVects, (long long)nThreads);
    int64_t nextStart = 0;
    for (uint32_t i = 0; i < nThreads; i++) {
      const int64_t curStart = nextStart;
      nextStart += perWorker.quot;
      if ((long long)i < perWorker.rem) {
        nextStart++;
      }
      thrs.emplace_back(AsyncStreamCopy, pDest + curStart, pSrc+curStart, nextStart-curStart);
    }
    for (uint32_t i = 0; i < nThreads; i++) {
      thrs[i].join();
    }
    _mm_sfence();
    auto elapsed = std::chrono::high_resolution_clock::now() - start;
    double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
    printf("Stream copy %d threads: %.3lf bytes/sec\n", (int)nThreads, cnDoubles * 2 * sizeof(double) / nSec);

    thrs.clear();
  }
}

Ответ 7

Как правило, быстрее не копировать. Независимо от того, можете ли вы адаптировать свою функцию, чтобы не копировать, я не знаю, но стоит ее заглянуть.

Ответ 8

Иногда такие функции, как memcpy, memset,... реализуются двумя разными способами:

  • один раз как действительная функция
  • один раз, как некоторая сборка, которая сразу же встроена

Не все компиляторы принимают версию встроенной сборки по умолчанию, ваш компилятор может использовать вариант функции по умолчанию, вызывая некоторые накладные расходы из-за вызова функции. Оставьте свой компилятор, чтобы посмотреть, как взять внутренний вариант функции (параметр командной строки, прагма,...).

Изменить: см. http://msdn.microsoft.com/en-us/library/tzkfha43%28VS.80%29.aspx для объяснения свойств встроенного компилятора Microsoft C.

Ответ 9

Проверьте руководство для компилятора/платформы. Для некоторых микропроцессоров и DSP-наборов с использованием memcpy гораздо медленнее, чем встроенные функции или DMA.

Ответ 10

Если ваша платформа поддерживает его, изучите, можете ли вы использовать системный вызов mmap(), чтобы оставить ваши данные в файле... как правило, ОС может справиться с этим лучше. И, как говорили все, избегайте копирования, если это вообще возможно; указатели - ваш друг в таких случаях.

Ответ 11

Вы можете посмотреть на это:

http://www.danielvik.com/2010/02/fast-memcpy-in-c.html

Еще одна идея, которую я постараюсь, - использовать методы COW для дублирования блока памяти и позволить ОС обрабатывать копирование по требованию, как только будет написана страница. Есть несколько советов, в которых используется mmap(): Можно ли сделать копию memcpy для копирования на Linux?

Ответ 12

Вы должны проверить код сборки, сгенерированный для вашего кода. Вы не хотите, чтобы вызов memcpy вызывал вызов функции memcpy в стандартной библиотеке - то, что вы хотите, - это повторный вызов лучшей инструкции ASM для копирования самого большого объема данных - что-то вроде rep movsq.

Как вы можете достичь этого? Ну, компилятор оптимизирует вызовы memcpy, заменив его простым mov, пока он знает, сколько данных он должен копировать. Это можно увидеть, если вы напишете memcpy с хорошо определенным значением (constexpr). Если компилятор не знает значения, он должен вернуться к реализации уровня байта memcpy - проблема заключается в том, что memcpy должен учитывать однобайтную детализацию. Он по-прежнему будет перемещать 128 бит за раз, но после каждого 128b ему нужно будет проверить, есть ли у него достаточно данных для копирования как 128b или он должен вернуться к 64 бит, а затем к 32 и 8 (я думаю, что 16 может быть субоптимальным во всяком случае, но я точно не знаю).

Итак, вы хотите либо сказать memcpy, что размер ваших данных с выражениями const, которые может оптимизировать компилятор. Таким образом, вызов memcpy не выполняется. То, что вы не хотите, - передать переменной memcpy переменную, которая будет известна только во время выполнения. Это переводит вызов функции и множество тестов, чтобы проверить лучшую инструкцию копирования. Иногда простая петля для этого лучше, чем memcpy по этой причине (исключая один вызов функции). И что вам действительно не нужно - передать memcpy нечетное количество байтов для копирования.

Ответ 13

Я предполагаю, что у вас должны быть огромные области памяти, которые вы хотите скопировать, если производительность memcpy стала для вас проблемой?

В этом случае я согласен с предложением nos выяснить, каким образом НЕ копировать материал.

Вместо того, чтобы копировать один огромный блок памяти, когда вам нужно его изменить, вам, вероятно, следует попробовать некоторые альтернативные структуры данных.

Не зная ничего о вашей проблемной области, я бы предложил хорошо рассмотреть постоянные структуры данных и либо реализовать одну из ваших собственных или повторное использование существующей реализации.

Ответ 14

nos прав, вы слишком много называете это.

Чтобы узнать, откуда вы его вызываете, и почему, просто приостановите его несколько раз под отладчиком и посмотрите на стек.

Ответ 15

память в память обычно поддерживается в наборе команд процессора, и memcpy обычно использует это. И это, как правило, самый быстрый способ.

Вы должны проверить, что именно делает ваш процессор. В Linux обратите внимание на эффективность swapi и эффективность виртуальной памяти с помощью sar -B 1 или vmstat 1 или путем поиска в /proc/memstat. Вы можете видеть, что ваша копия должна выталкивать много страниц на свободное место или читать их и т.д.

Это означает, что ваша проблема не в том, что вы используете для копии, а в том, как ваша система использует память. Возможно, вам придется уменьшить кеш файлов или начать запись раньше, или заблокировать страницы в памяти и т.д.