Вопрос

Резюме:

memcpy кажется неспособным передать более 2 ГБ / сек в мою систему в реальном или испытательном приложении. Что я могу сделать, чтобы получить быстрее копии памяти памяти?

Полная информация:

В рамках приложения для захвата данных (используя некоторые специализированные аппаратные средства), мне нужно скопировать около 3 ГБ / с из временных буферов в основную память. Чтобы получить данные, я предоставляю аппаратный драйвер с серией буферов (2 МБ каждый). Аппаратные данные DAMA для каждого буфера, а затем уведомляют мою программу, когда каждый буфер заполнен. Моя программа опустошает буфер (memcpy на другой, больший блок оперативной памяти) и повторяет обработанный буфер на карту, чтобы снова заполнить. У меня проблемы с MEMCPY, перемещая данные достаточно быстро. Похоже, что копия памяти к памяти должна быть достаточно быстро для поддержки 3GB / SEC на оборудовании, на которой я работаю. Lavalys Everest дает мне результат тестов копии памяти 9337MB / SEC, но я не могу добраться от этих скоростей с MEMCPY, даже в простой тестовой программе.

Я выделил проблему производительности, добавив / удаляя MEMCPY CALL внутри кода обработки буфера. Без мемкпи я могу запускать полную скорость передачи данных - около 3 ГБ / с. С включенной MEMCPY я ограничен около 550 МБ / с (с помощью текущего компилятора).

Чтобы ориентировать MEMCPY в мою систему, я написал отдельную программу тестирования, которая просто называет MEMCPY на некоторых блоках данных. (Я опубликовал код ниже). Чтобы сделать выключатель, если он даст необходимую производительность. Однако, прежде чем слепо двигаться, я хотел убедиться, что это решит мои проблемы с производительностью MEMCPY.

Visual C ++ 2010: 1900 МБ / сек

Ni CVI 2009: 550 МБ / сек

Хотя я не удивлен, что CVI значительно медленнее, чем Visual Studio, я удивлен, что производительность MEMCPY - это низкая. Хотя я не уверен, что это напрямую сопоставимо, это намного ниже, чем пропускная пропускная способность ориентира Everest. Хотя мне не нужно совсем этот уровень производительности, необходим минимум 3 ГБ / с. Конечно, стандартная библиотечная реализация не может быть намного хуже, чем воспользоваться Everest!

Что, если что-то, я могу сделать, чтобы сделать Memcpy быстрее в этой ситуации?


Оборудование для аппаратного обеспечения: AMD Magny Cours - 4x Other Core Core 128 GB DDR3 Windows Server 2003 Enterprise X64

Программа тестирования:

#include <windows.h>
#include <stdio.h>

const size_t NUM_ELEMENTS = 2*1024 * 1024;
const size_t ITERATIONS = 10000;

int main (int argc, char *argv[])
{
    LARGE_INTEGER start, stop, frequency;

    QueryPerformanceFrequency(&frequency);

    unsigned short * src = (unsigned short *) malloc(sizeof(unsigned short) * NUM_ELEMENTS);
    unsigned short * dest = (unsigned short *) malloc(sizeof(unsigned short) * NUM_ELEMENTS);

    for(int ctr = 0; ctr < NUM_ELEMENTS; ctr++)
    {
        src[ctr] = rand();
    }

    QueryPerformanceCounter(&start);

    for(int iter = 0; iter < ITERATIONS; iter++)
        memcpy(dest, src, NUM_ELEMENTS * sizeof(unsigned short));

    QueryPerformanceCounter(&stop);

    __int64 duration = stop.QuadPart - start.QuadPart;

    double duration_d = (double)duration / (double) frequency.QuadPart;

    double bytes_sec = (ITERATIONS * (NUM_ELEMENTS/1024/1024) * sizeof(unsigned short)) / duration_d;

    printf("Duration: %.5lfs for %d iterations, %.3lfMB/sec\n", duration_d, ITERATIONS, bytes_sec);

    free(src);
    free(dest);

    getchar();

    return 0;
}

Редактировать: Если у вас есть дополнительные пять минут и хотите внести свой вклад, вы можете запустить вышеуказанный код на вашем компьютере и опубликовать свое время в качестве комментариев?

Это было полезно?

Решение

Я нашел способ увеличить скорость в этой ситуации. Я написал многопоточную версию MEMCPY, разделив область, которая будет скопирована между потоками. Вот некоторые номера масштабирования производительности для установленного размера блока, используя один и тот же код времени, который нашел выше. Я понятия не имел, что производительность, особенно для этого небольшого размера блока, масштабируется на это много потоков. Я подозреваю, что это что-то связано с большим количеством контроллеров памяти (16) на этой машине.

Performance (10000x 4MB block memcpy):

 1 thread :  1826 MB/sec
 2 threads:  3118 MB/sec
 3 threads:  4121 MB/sec
 4 threads: 10020 MB/sec
 5 threads: 12848 MB/sec
 6 threads: 14340 MB/sec
 8 threads: 17892 MB/sec
10 threads: 21781 MB/sec
12 threads: 25721 MB/sec
14 threads: 25318 MB/sec
16 threads: 19965 MB/sec
24 threads: 13158 MB/sec
32 threads: 12497 MB/sec

Я не понимаю огромный прыжок производительности от 3 до 4 потоков. Что бы вызвать прыжок такой?

Я включил код MEMCPY, который я писал ниже для других, который может работать в эту же проблему. Обратите внимание, что в этом коде нет проверки ошибок. Это может быть добавлено для вашего приложения.

#define NUM_CPY_THREADS 4

HANDLE hCopyThreads[NUM_CPY_THREADS] = {0};
HANDLE hCopyStartSemaphores[NUM_CPY_THREADS] = {0};
HANDLE hCopyStopSemaphores[NUM_CPY_THREADS] = {0};
typedef struct
{
    int ct;
    void * src, * dest;
    size_t size;
} mt_cpy_t;

mt_cpy_t mtParamters[NUM_CPY_THREADS] = {0};

DWORD WINAPI thread_copy_proc(LPVOID param)
{
    mt_cpy_t * p = (mt_cpy_t * ) param;

    while(1)
    {
        WaitForSingleObject(hCopyStartSemaphores[p->ct], INFINITE);
        memcpy(p->dest, p->src, p->size);
        ReleaseSemaphore(hCopyStopSemaphores[p->ct], 1, NULL);
    }

    return 0;
}

int startCopyThreads()
{
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
    {
        hCopyStartSemaphores[ctr] = CreateSemaphore(NULL, 0, 1, NULL);
        hCopyStopSemaphores[ctr] = CreateSemaphore(NULL, 0, 1, NULL);
        mtParamters[ctr].ct = ctr;
        hCopyThreads[ctr] = CreateThread(0, 0, thread_copy_proc, &mtParamters[ctr], 0, NULL); 
    }

    return 0;
}

void * mt_memcpy(void * dest, void * src, size_t bytes)
{
    //set up parameters
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
    {
        mtParamters[ctr].dest = (char *) dest + ctr * bytes / NUM_CPY_THREADS;
        mtParamters[ctr].src = (char *) src + ctr * bytes / NUM_CPY_THREADS;
        mtParamters[ctr].size = (ctr + 1) * bytes / NUM_CPY_THREADS - ctr * bytes / NUM_CPY_THREADS;
    }

    //release semaphores to start computation
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
        ReleaseSemaphore(hCopyStartSemaphores[ctr], 1, NULL);

    //wait for all threads to finish
    WaitForMultipleObjects(NUM_CPY_THREADS, hCopyStopSemaphores, TRUE, INFINITE);

    return dest;
}

int stopCopyThreads()
{
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
    {
        TerminateThread(hCopyThreads[ctr], 0);
        CloseHandle(hCopyStartSemaphores[ctr]);
        CloseHandle(hCopyStopSemaphores[ctr]);
    }
    return 0;
}

Другие советы

Я не уверен, что это сделано во время выполнения или если вам нужно сделать это компиляционное время, но у вас должно быть SSE или подобные расширения, включенные в качестве векторного блока часто можно записать 128 бит в память по сравнению с 64 битами для процессора.

Пытаться Это реализация.

Да, и убедитесь, что оба Источник и пункт назначения выровнен к 128 битам. Если ваш источник и пункт назначения не выровнены, соответствующие друг другу, ваш memcpy () должен сделать серьезную магию. :)

У вас есть несколько барьеров для получения необходимой производительности памяти:

  1. Пропускная способность - есть предел, насколько быстро данные могут перемещаться от памяти в CPU и снова обратно. В соответствии с Эта статья Википедии, 266 МГц DDR3 RAM имеет верхний предел около 17 ГБ / с. Теперь, с мемккостью вам нужно вдвое, чтобы получить максимальную скорость передачи, поскольку данные прочитаны, а затем записаны. Из ваших результатов вашего эталона похоже, что вы не запустите самую быструю оперативную память в вашей системе. Если вы можете себе это позволить, обновить материнскую плату / RAM (и она не будет дешевым, overclockers в Великобритании в настоящее время имеет 3x4 ГБ PC16000 в £ 400)

  2. ОС - Windows - это упреждающая многозадачная ОС, поэтому каждый часто ваш процесс будет приостановлен, чтобы позволить другим процессам взглянуть и делать вещи. Это защелачивает ваши кеши и остановит ваш трансфер. В худшем случае весь ваш процесс может быть кэширован на диск!

  3. CPU - перемещение данных имеет долгий путь: RAM -> L2 кэш -> кэш L1 -> CPU -> L1 -> L2 -> ОЗУ. Может даже быть кэш L3. Если вы хотите привлечь процессор, вы действительно хотите загружать L2, когда копирование L1. К сожалению, современные процессоры могут проходить через блок кеша L1 быстрее, чем время, необходимое для загрузки L1. CPU имеет контроллер памяти, который много помогает в этих случаях, когда ваши потоковые данные в CPU последовательно, но у вас все еще будут проблемы.

Конечно, тем быстрее сделать что-то сделать, чтобы не сделать это. Можно ли записанные данные быть написаны в любом месте ОЗУ или является буфером, используемым в фиксированном месте. Если вы можете написать его нигде, то вам вообще не нужна мемкпи. Если он фиксируется, не могли бы вы обработать данные на месте и использовать систему двойного буфера? То есть начать захватывать данные и когда он наполовину заполнен, начните обработку первой половины данных. Когда буфер заполнен, начните запись захваченных данных в начало и обрабатывать вторую половину. Это требует, чтобы алгоритм мог обработать данные быстрее, чем карта Capture, производит его. Это также предполагает, что данные отбрасываются после обработки. Эффективно, это мемкпина с преобразованием как часть процесса копирования, поэтому у вас есть:

load -> transform -> save
\--/                 \--/
 capture card        RAM
   buffer

вместо:

load -> save -> load -> transform -> save
\-----------/
memcpy from
capture card
buffer to RAM

Или получить быстрее RAM!

Редактировать: Другой вариант - обработать данные между источником данных и ПК - не могли бы вы поставить DSP / FPGA вообще вообще? Пользовательское оборудование всегда будет быстрее, чем процессор общего назначения.

Другая мысль: Прошло некоторое время, так как я проделал любые высокоэффективные графические вещи, но не могли бы вы DMA данные в видеокарту, а затем снова DMA? Вы могли бы даже воспользоваться CUDA, чтобы сделать часть обработки. Это потребовало бы процессор из контура передачи памяти в целом.

Однозначное, в том, что ваш процесс (и, следовательно, производительность memcpy()) влияет на планирование задач ОС - трудно сказать, сколько фактора это в ваших моментах, Bu Tit трудно контролировать. Операция устройства DMA не зависит от этого, поскольку он не работает на процессоре, когда он выгнан. Поскольку ваша заявка является фактическим приложением в реальном времени, хотя, вы можете поэкспериментировать с настройками приоритета Windows «Process / Thream), если вы еще этого не сделали. Просто имейте в виду, что вы должны быть осторожны об этом, потому что он может оказать действительно негативное влияние в других процессах (и пользовательский опыт на машине).

Другое, что следует помнить, что виртуализация памяти ОС может оказать воздействие здесь - если страницы памяти вы копируете, на самом деле не поддерживаются физическими страницами RAM, memcpy() Операция будет не вина в ОС, чтобы получить эту физическую поддержку на месте. Ваши страницы DMA могут быть заблокированы в физическую память (поскольку они должны быть для операции DMA), поэтому исходная память к memcpy() скорее всего, не проблема в этом отношении. Вы можете рассмотреть возможность использования Win32 VirtualAlloc() API, чтобы гарантировать, что ваша назначение памяти для memcpy() совершается (я думаю, VirtualAlloc() Правильная API для этого, но может быть лучше, что я забыл - прошел некоторое время, так как я должен был сделать что-то подобное).

Наконец, посмотрите, можете ли вы использовать Техника, объясненная Skizziz чтобы избежать memcpy() Всего - это ваша лучшая ставка, если разрешение ресурсов.

Прежде всего, вам необходимо проверить, что память выровнена на 16 байт-границе, в противном случае вы получаете штрафы. Это самое главное.

Если вам не нужно стандартное решение для совместимого по стандарту, вы можете проверить, улучшится ли все, используя некоторое определенное расширение компилятора, такое как memcpy64 (Проверьте со своим компилятором DOC, если есть что-то доступное). Факт это так memcpyДолжен быть в состоянии справиться с одной байтовой копией, но перемещение 4 или 8 байтов за раз намного быстрее, если у вас нет этого ограничения.

Опять же, это вариант для вас, чтобы написать встроенный сборнический код?

Возможно, вы можете объяснить еще немного о том, как вы обрабатываете большую область памяти?

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

Или вы используете memcpy Для более чем просто копирование? Возможно, вы используете большую площадь памяти для создания последовательного потока данных из того, что вы поймали? Особенно, если вы обрабатываете один символ за раз, вы можете встретиться на полпути. Например, может быть возможно адаптировать ваш код обработки для размещения потока, представленного как «массив буферов», а не «непрерывную область памяти».

Вы можете написать лучшую реализацию MEMCPY с использованием регистров SSE2. Версия VC2010 делает это уже. Таким образом, вопрос больше, если вы передаете его, выровненные память.

Может быть, вы можете сделать лучше, чем версия VC 2010, но это нужно некоторое понимание, как это сделать.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top