programing

memcpy 대신 더 빠른 방법?

randomtip 2022. 7. 21. 21:27
반응형

memcpy 대신 더 빠른 방법?

저는 memcpy를 하는 기능을 가지고 있지만, 엄청난 사이클을 소비하고 있습니다.메모리 일부를 이동하는 데 memcpy를 사용하는 것보다 더 빠른 대체/접근 방법이 있습니까?

이것은, AVX2 명령 세트가 있는 x86_64 에 대한 회답입니다.SIMD를 사용하는 ARM/AARCH64에도 유사한 것이 적용될 수 있습니다.

에서는 (2각 DDR4) 는 1 GB보다 1 배 .memcpy()MSVC++2017년, MSVC++2017년.양쪽 메모리 채널을 2개의 DDR4 모듈로 채우는 경우(즉, 4개의 DDR4 슬롯이 모두 비지 상태), 메모리 복사를 2배 고속화할 수 있습니다.트리플(쿼드) 채널메모리 시스템의 경우, 코드를 유사한 AVX512 코드로 확장하면, 1.5(2.0)배의 고속 메모리 카피를 얻을 수 있습니다.모든 슬롯이 비지 상태의 AVX2 전용 트리플/쿼드 채널시스템에서는 32바이트(쿼드 채널시스템은 48바이트, 쿼드 채널시스템은 64바이트)를 동시에 로드/저장할 필요가 있기 때문에 속도가 빨라지지 않을 것으로 예상됩니다.일부 시스템에서는 멀티스레딩이 AVX512 또는 AVX2를 사용하지 않아도 이를 완화할 수 있습니다.

이 복사 코드는 크기가 32의 배수이고 블록이 32바이트로 정렬된 대용량 메모리 블록을 복사하고 있다고 가정합니다.

비배수 크기 및 비정렬 블록의 경우 블록 헤드 및 테일 폭을 16(SSE4.1), 8, 4, 2, 마지막으로 1바이트로 줄이는 프롤로그/에필로그 코드를 동시에 쓸 수 있다.이 있습니다.__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();
}

카피시에 하고 있는 「」가 없는 , 「CPU」가 없는 ).CPU " " " " " " ( " , AVX " )_stream_복사 속도가 시스템에서 여러 번 저하됩니다.

DDR4 메모리는 2.6입니다.어떤 어레이에서 다른 어레이로 8GB의 데이터를 복사하면 다음과 같은 속도를 얻을 수 있습니다.

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

이러한 측정에서는 입력 버퍼와 출력 버퍼의 총 크기가 경과된 초수로 나누어집니다.배열의 각 바이트에는 2개의 메모리 액세스가 있기 때문에 하나는 입력 배열에서 바이트를 읽기 위한 액세스이고 다른 하나는 출력 배열에 바이트를 쓰기 위한 액세스입니다.즉, 어레이 간에 8GB를 복사할 경우 16GB의 메모리 액세스 작업을 수행할 수 있습니다.

정도의 을 사용하면 1.되므로 총 은 1.44배입니다.따라서 전체적으로는memcpy()2입니다. 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();
  }
}

memcpy메모리 내에서 바이트를 복사할 수 있는 가장 빠른 방법이 될 수 있습니다.더 빠른 것이 필요한 경우 - 예를 들어 데이터 자체가 아닌 포인터만 교환하는 등 복사하지 않는 방법을 생각해 보십시오.

여기 인라인 가능한 대체 C 버전의 memcpy가 있습니다.이 버전을 사용한 어플리케이션에서는 Arm64용 GCC의 memcpy보다 50% 정도 성능이 우수합니다.64비트 플랫폼에 의존하지 않습니다.테일 처리는 사용 인스턴스가 좀 더 빠른 속도로 필요하지 않은 경우 삭제할 수 있습니다.uint32_t 어레이를 복사합니다. 작은 데이터 유형은 테스트되지 않지만 작동할 수 있습니다.다른 데이터 유형에 적응할 수 있습니다. 64비트 복사(두 개의 인덱스가 동시에 복사됨).32비트도 동작하지만 속도가 느려집니다.Neoscrypt 프로젝트의 크레딧.

    static inline void newmemcpy(void *__restrict__ dstp, 
                  void *__restrict__ srcp, uint len)
        {
            ulong *dst = (ulong *) dstp;
            ulong *src = (ulong *) srcp;
            uint i, tail;

            for(i = 0; i < (len / sizeof(ulong)); i++)
                *dst++ = *src++;
            /*
              Remove below if your application does not need it.
              If console application, you can uncomment the printf to test
              whether tail processing is being used.
            */
            tail = len & (sizeof(ulong) - 1);
            if(tail) {
                //printf("tailused\n");
                uchar *dstb = (uchar *) dstp;
                uchar *srcb = (uchar *) srcp;

                for(i = len - tail; i < len; i++)
                    dstb[i] = srcb[i];
            }
        }

다음은 Visual C++/Ryzen 1700 벤치마크입니다.

벤치마크에서는 128MiB 링 버퍼에서 16KiB(중복되지 않음) 청크를 8*8192회 복사합니다(총 1GiB의 데이터가 복사됩니다).

그런 다음 결과를 정규화하면 벽면 클럭 시간(밀리초)과 60Hz의 throughput 값(16.667밀리초 동안 처리할 수 있는 데이터 양)이 표시됩니다.

memcpy                           2.761 milliseconds ( 772.555 MiB/frame)

와 같이 내장되어 있습니다.memcpy르지지 ?? ?? ????

64-wide load/store              39.889 milliseconds (  427.853 MiB/frame)
32-wide load/store              33.765 milliseconds (  505.450 MiB/frame)
16-wide load/store              24.033 milliseconds (  710.129 MiB/frame)
 8-wide load/store              23.962 milliseconds (  712.245 MiB/frame)
 4-wide load/store              22.965 milliseconds (  743.176 MiB/frame)
 2-wide load/store              22.573 milliseconds (  756.072 MiB/frame)
 1-wide load/store              35.032 milliseconds (  487.169 MiB/frame)

위는 아래 코드에 따라 달라집니다.n.

// n is the "wideness" from the benchmark

auto src = (__m128i*)get_src_chunk();
auto dst = (__m128i*)get_dst_chunk();

for (int32_t i = 0; i < (16 * 1024) / (16 * n); i += n) {
  __m128i temp[n];

  for (int32_t i = 0; i < n; i++) {
    temp[i] = _mm_loadu_si128(dst++);
  }

  for (int32_t i = 0; i < n; i++) {
    _mm_store_si128(src++, temp[i]);
  }
}

이것이 내가 가지고 있는 결과에 대한 나의 최선의 추측이다.Zen 마이크로아키텍처에 대해 아는 한 사이클당 32바이트밖에 취득할 수 없습니다.그렇기 때문에 16바이트 로드/스토어당 최대 2배입니다.

  • 를 1x로 합니다.xmm0, 128비트
  • 를 2x로 합니다.ymm0, 256비트

에 속도가 약 으로는 2배 이상 빨라집니다.memcpy(또는 플랫폼에 적절한 최적화를 유효하게 했을 경우)를 실행합니다.

또한 캐시 대역폭이 더 이상 빨라지지 않기 때문에 이를 더 빠르게 만드는 것도 불가능합니다.이 점은 매우 중요한 사실이라고 생각합니다.기억에 얽매여 보다 빠른 솔루션을 찾고 있다면 매우 오랜 시간 동안 솔루션을 찾을 수 있기 때문입니다.

좀 더 자세히 알려주세요.i386 아키텍처에서는 memcpy가 가장 빠른 복사 방법일 수 있습니다.그러나 컴파일러에 최적화된 버전이 없는 다른 아키텍처에서는 memcpy 함수를 다시 쓰는 것이 가장 좋습니다.어셈블리 언어를 사용한 커스텀 ARM 아키텍처에서 이 작업을 수행했습니다.대용량 메모리 청크를 전송하는 경우 DMA가 원하는 답일 수 있습니다.

아키텍처, 운영체제(해당하는 경우)에 대해 자세히 알려주세요.

는 「」를 .memcpy()이미 타겟 플랫폼에 가장 빠른 방법을 제공합니다.

코드용으로 생성된 어셈블리 코드를 확인해야 합니다.이 원하지 않는 것은 '우리'가 입니다.memcpy을 생성하다memcpy라이브러리의 - 하기 위해 의 ASM 를 들어, 「」- 「」 「」 「」 「ASM」 입니다.rep movsq.

떻게 할할 ???memcpy을 단순한 으로 mov복사해야 할 데이터의 양을 알고 있으면 됩니다. 알 수 요.memcpy태연하게constexpr의 값컴파일러는 바이트 구현으로 합니다.memcpy예요. -그게 문제예요memcpy1번으로 하다한 번에 128비트가 이동하지만 128b마다 128b로 복사하기에 충분한 데이터가 있는지, 64비트로 폴백한 후 32비트와 8비트로 폴백해야 하는지 확인해야 합니다(어차피 16비트는 최적이라고 생각합니다만, 확실히는 모르겠습니다).

당신이 은 '에게 말할 수 것'입니다.memcpy컴파일러가 최적화할 수 있는 const 표현식의 데이터 크기가 어떻게 됩니까? to to에 콜이 .memcpy 않는 에게 넘겨주는 memcpy런타임에만 알 수 있는 변수입니다.이는 함수 호출과 최적의 복사 명령을 확인하기 위한 수많은 테스트로 이어집니다.에 따라서는 단순한 이 is우 is sometimes sometimes is sometimes is sometimes is is is is is is is is is is sometimes sometimes보다 나을 수 .memcpy이러한 이유로 (1개의 함수콜의 송신)그리고 당신이 정말 원하지 않는 은 당신에게 넘겨주는 것이다.memcpy복사할 홀수 바이트 수

memcpy의 퍼포먼스가 문제가 된다면 복사하고 싶은 메모리 영역이 매우 넓을 것이라고 생각합니다.

이 경우 복사하지 않는 방법을 찾으라는 nos의 제안에 동의합니다.

변경해야 할 때마다 복사해야 하는 메모리 덩어리가 하나 있는 대신 다른 데이터 구조를 시도해 보는 것이 좋습니다.

고객님의 문제 영역에 대해 제대로 알지 못하는 상태에서 지속적인 데이터 구조를 자세히 살펴보고 고객님의 데이터 구조를 구현하거나 기존 구현을 재사용할 것을 권장합니다.

이 함수로 인해 포인터(입력 인수) 중 하나가 32비트에 정렬되지 않은 경우 데이터 중단 예외가 발생할 수 있습니다.

사실 memcpy가 가장 빠른 방법은 아닙니다. 특히 여러 번 전화를 걸면 더욱 그렇습니다.또한 속도를 높이기 위해 필요한 코드가 몇 개 있었는데, memcpy는 불필요한 체크가 너무 많아서 속도가 느립니다.예를 들어, 행선지 및 송신원메모리 블록이 겹치는지, 블록의 전면이 아닌 후면으로부터 복사를 개시할지를 확인합니다.이러한 고려사항에 신경 쓰지 않는다면 분명히 훨씬 더 잘 할 수 있을 것입니다.몇 가지 코드가 있습니다만, 여기 더 나은 버전이 있습니다.

이미지 처리에 매우 빠른 memcpy?

검색하면 다른 구현도 찾을 수 있습니다.하지만 진정한 속도를 위해서는 어셈블리 버전이 필요합니다.

일반적으로 복사를 전혀 하지 않는 것이 더 빠릅니다.카피하지 않도록 당신의 기능을 조정할 수 있을지는 모르겠지만 살펴볼 가치가 있습니다.

memcpy, memset 등의 함수는 다음 두 가지 방법으로 구현될 수 있습니다.

  • 한때는 실제 기능으로서
  • 한 번은 바로 삽입된 조립품처럼

모든 컴파일러가 기본적으로 인라인 어셈블리 버전을 사용하는 것은 아닙니다.사용하는 컴파일러는 기본적으로 함수 배리언트를 사용하여 함수 호출로 인해 약간의 오버헤드가 발생할 수 있습니다.함수의 고유 변형(명령줄 옵션, 플러그마 등)을 가져오는 방법을 보려면 컴파일러를 확인하십시오.

편집: Microsoft C 컴파일러의 내장 기능에 대해서는, http://msdn.microsoft.com/en-us/library/tzkfha43%28VS.80%29.aspx 를 참조해 주세요.

컴파일러/플랫폼 매뉴얼을 참조하십시오.일부 마이크로프로세서 및 DSP킷에서는 memcpy를 사용하는 것이 내장 함수 또는 DMA 조작보다 훨씬 느립니다.

플랫폼이 지원하는 경우 mmap() 시스템 호출을 사용하여 데이터를 파일에 남길 수 있는지 확인하십시오.일반적으로 OS는 이를 더 잘 관리할 수 있습니다.그리고 모두가 말했듯이, 가능하면 복사는 피하세요.이런 경우 포인터는 당신의 친구입니다.

다음 내용을 확인하시기 바랍니다.

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

또 하나의 방법은 COW 기술을 사용하여 메모리 블록을 복제하고 페이지가 기록되는 즉시 OS가 온 디맨드로 복사를 처리할 수 있도록 하는 것입니다.여기에서는 다음을 사용하여 몇 가지 힌트를 제공합니다.mmap()Linux에서 copy-on-write memcpy를 실행할 수 있습니까?

memcpy는 보통 CPU 명령어세트로 지원되며, memcpy는 보통 그것을 사용합니다.그리고 이것이 보통 가장 빠른 방법이다.

CPU가 정확히 무엇을 하고 있는지 확인해야 합니다.Linux의 경우 sar -B 1 또는 vmstat 1을 사용하거나 /proc/memstat를 검색하여 swapi 입력 및 출력과 가상 메모리의 효과를 확인합니다.카피가 많은 페이지를 밀어내 빈 공간을 확보하거나 읽어 들이는 등의 작업을 수행해야 하는 것을 알 수 있습니다.

즉, 복사에 사용하는 것이 아니라, 시스템의 메모리 사용 방법에 문제가 있습니다.파일 캐시를 줄이거나 더 일찍 쓰기를 시작하거나 메모리의 페이지를 잠그는 등의 작업이 필요할 수 있습니다.

이 질문은 12년 전의 것으로, 또 다른 답을 쓰고 있습니다.하지만 여전히 검색어에 올라오고 해답은 항상 진화하고 있습니다.

아직 아무도 애그너 포그의 애슬립에 대해 언급하지 않았다니 놀랍다.
memcpy()memmove(), memset(), strlen() 등의 다른 많은 SIMD 최적화 Clib 치환의 드롭.
AVX-512 명령 세트까지, 사용의 CPU가 서포트하고 있는 최적인 것을 자동적으로 사용합니다.x86/AMD64 립스

언급URL : https://stackoverflow.com/questions/2963898/faster-alternative-to-memcpy

반응형