Memcpy

Содержание:

Почему memcpy намного медленнее, чем memmove или ручная копия на сервере?

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

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

Ответ заключается в потоковые (иначе временные) магазины. Libc версия вы используете использует их, но не. Вы подтвердили столько же со своим «наивным» который также не использует их, а также моя настройка как использовать потоковые хранилища (медленно), так и нет (быстро).

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

  • (А) Они препятствуют тому, чтобы предварительная выборка вводила строки, которые должны быть сохранены, в кэш, что обеспечивает больший параллелизм, поскольку аппаратное обеспечение предварительной выборки имеет другие выделенные буферы помимо 10. заполнить буферы что требует нагрузка / магазины используют.
  • (В) E5-2680, как известно, для потоковых магазинов.

Обе проблемы лучше объясняются цитатами из Джона Маккальпина в приведенной выше ветке. На тему эффективности предварительной выборки и потоковых магазинов :

… а затем, по-видимому, гораздо более длительное время ожидания для потоковых магазинов на E5, :

В частности, доктор МакКалпин измерил замедление для E5 в ~ 1,8 раза по сравнению с чипом с «клиентским» ядром, но замедление в 2,5 раза, о котором сообщают OP, согласуется с этим, поскольку в STREAM TRIAD сообщается о 1,8-кратном балле соотношение нагрузок 2: 1: магазины, в то время как в 1: 1, и магазины являются проблемной частью.

Это не делает потоковую передачу плохой вещью — по сути, вы тратите деньги с задержкой для меньшего общего потребления полосы пропускания. Вы получаете меньшую пропускную способность, потому что вы ограничены в параллелизме при использовании одного ядра, но вы избегаете всего трафика чтения для владения, поэтому вы, вероятно, увидите (небольшое) преимущество, если вы запустите тест одновременно на всех ядрах.

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

COLOPHON top

       This page is part of release 5.08 of the Linux man-pages project.  A
       description of the project, information about reporting bugs, and the
       latest version of this page, can be found at
       https://www.kernel.org/doc/man-pages/.

                                 2017-09-15                        MEMCPY(3)

Pages that refer to this page:
bcopy(3), 
bstring(3), 
cmsg(3), 
cmsg_align(3), 
CMSG_ALIGN(3), 
cmsg_data(3), 
CMSG_DATA(3), 
cmsg_firsthdr(3), 
CMSG_FIRSTHDR(3), 
cmsg_len(3), 
CMSG_LEN(3), 
cmsg_nxthdr(3), 
CMSG_NXTHDR(3), 
cmsg_space(3), 
CMSG_SPACE(3), 
cpu_alloc(3), 
CPU_ALLOC(3), 
cpu_alloc_size(3), 
CPU_ALLOC_SIZE(3), 
cpu_and(3), 
CPU_AND(3), 
cpu_and_s(3), 
CPU_AND_S(3), 
cpu_clr(3), 
CPU_CLR(3), 
cpu_clr_s(3), 
CPU_CLR_S(3), 
cpu_count(3), 
CPU_COUNT(3), 
cpu_count_s(3), 
CPU_COUNT_S(3), 
cpu_equal(3), 
CPU_EQUAL(3), 
cpu_equal_s(3), 
CPU_EQUAL_S(3), 
cpu_free(3), 
CPU_FREE(3), 
cpu_isset(3), 
CPU_ISSET(3), 
cpu_isset_s(3), 
CPU_ISSET_S(3), 
cpu_or(3), 
CPU_OR(3), 
cpu_or_s(3), 
CPU_OR_S(3), 
cpu_set(3), 
CPU_SET(3), 
cpu_set_s(3), 
CPU_SET_S(3), 
cpu_xor(3), 
CPU_XOR(3), 
cpu_xor_s(3), 
CPU_XOR_S(3), 
cpu_zero(3), 
CPU_ZERO(3), 
cpu_zero_s(3), 
CPU_ZERO_S(3), 
memccpy(3), 
memmove(3), 
mempcpy(3), 
stpcpy(3), 
strcat(3), 
strcpy(3), 
strncat(3), 
strncpy(3), 
wmemcpy(3), 
wmempcpy(3), 
feature_test_macros(7), 
signal-safety(7)

Решение

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

  • Если вы измените направление своей наивности (т.е. преобразовать в ), тогда вы можете получить гораздо худшую производительность, чем для прямого направления (для меня ~ 637 мс). Произошло изменение в в glibc 2.12, который выявил несколько ошибок для вызова на перекрывающихся буферах (http://lwn.net/Articles/414467/) и я считаю, что проблема была вызвана переходом на версию это работает в обратном направлении. Таким образом, обратная или прямая копии могут объяснить / несоответствие.
  • Кажется, лучше не использовать временные магазины. Многие оптимизированы реализации переключаются на временные хранилища (которые не кэшируются) для больших буферов (то есть больше, чем кэш последнего уровня). Я проверил версию Memcpy Агнера Фога () и обнаружил, что скорость была примерно такой же, как у версии в , Тем не мение, имеет функцию (), что позволяет установить порог, выше которого используются не временные хранилища. Установка этого предела на 8 ГБ (или просто больше, чем буфер 1 ГБ), чтобы избежать невременных накопителей, удвоила производительность в моем случае (время до 176 мс). Конечно, это только соответствовало наивному исполнению в прямом направлении, поэтому оно не звездное.
  • BIOS в этих системах позволяет включать / отключать четыре разных аппаратных средства предварительной выборки (MLC Streamer Prefetcher, MLC Spatial Prefetcher, DCU Streamer Prefetcher и DCU IP Prefetcher). Я пытался отключить каждый из них, но в лучшем случае поддерживал паритет производительности и снижал производительность для некоторых параметров.
  • Отключение режима оперативной памяти (RAPL) DRAM не влияет.
  • У меня есть доступ к другим системам Supermicro под управлением Fedora 19 (glibc 2.17). С платой Supermicro X9DRG-HF, процессорами Fedora 19 и Xeon E5-2670 я вижу такую ​​же производительность, как и выше. На плате Supermicro X10SLM-F с одним гнездом, работающей на Xeon E3-1275 v3 (Haswell) и Fedora 19, я вижу 9,6 ГБ / с для (104ms). Оперативная память в системе Haswell составляет DDR3-1600 (так же, как и в других системах).

ОБНОВЛЕНИЕ

  • Я установил управление питанием процессора на максимальную производительность и отключил гиперпоточность в BIOS. На основе затем ядра работали на частоте 3 ГГц. Однако это странным образом уменьшило производительность памяти примерно на 10%.
  • memtest86 + 4.10 сообщает пропускную способность основной памяти 9091 МБ / с. Я не мог найти, соответствует ли это чтению, записи или копированию.
  • STREAM сообщает о копировании 13422 МБ / с, но они считают байты как прочитанными, так и записанными, так что это соответствует ~ 6,5 ГБ / с, если мы хотим сравнить с приведенными выше результатами.

23

Почему серверная часть еще медленнее при использовании обычных магазинов?

Даже после устранения проблемы с временным магазином, вы еще видя примерно замедление на серверных частях. Что дает?

Ну, это распространенная ошибка, что процессоры сервера быстрее во всех отношениях быстрее или, по крайней мере, равны своим клиентским аналогам. Это просто неправда — то, что вы платите (часто по 2000 долларов за чип или около того) на серверных частях, это в основном (а) больше ядер (б) больше каналов памяти (в) поддержка большей общей оперативной памяти (d) » такие функции, как ECC, функции виртуализации и т. д.5.

Фактически, с точки зрения задержки серверные части обычно равны или медленнее своего клиента4 частей. Когда речь идет о задержке памяти, это особенно верно, потому что:

  • Серверные части имеют более масштабируемый, но сложный «неядерный», который часто должен поддерживать гораздо больше ядер, и, следовательно, путь к оперативной памяти длиннее.
  • Серверные части поддерживают больше оперативной памяти (100 ГБ или несколько ТБ), что часто требует электрические буферы поддерживать такое большое количество.
  • Как и в случае с OP, серверные части, как правило, состоят из нескольких сокетов, что добавляет проблемы связности между сокетами в путь памяти.

Поэтому обычно серверные части имеют задержку на 40–60% больше, чем клиентские части. Для E5 вы, вероятно, обнаружите, что ~ 80 нс в ОЗУ, а клиентские части ближе к 50 нс.

Поэтому все, что связано с задержкой ОЗУ, будет работать медленнее на серверных частях, и, как оказалось, на одном ядре задержка ограничена. это сбивает с толку, потому что кажется как измерение пропускной способности, верно? Как уже было сказано выше, у одного ядра недостаточно ресурсов, чтобы поддерживать достаточное количество запросов к оперативной памяти за раз, чтобы приблизиться к пропускной способности ОЗУ.6, поэтому производительность напрямую зависит от времени ожидания.

С другой стороны, клиентские чипы имеют меньшую задержку и меньшую пропускную способность, поэтому одно ядро ​​становится намного ближе к насыщению пропускной способности (именно поэтому потоковые хранилища являются большим выигрышем для клиентских частей — когда даже одно ядро ​​может приблизиться к Пропускная способность ОЗУ, 50% сокращение пропускной способности хранилища, которое предлагает потоковое хранилище, очень помогает.

12.2.2. Копирование памяти: memcpy(), memmove() и memccpy()

Скрыть рекламу в статье

12.2.2. Копирование памяти: , и

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

Это простейшая функция. Она копирует байтов из в . Она не обрабатывает перекрывающиеся области памяти. Функция возвращает .

Подобно , она также копирует байтов из в . Однако, она обрабатывает перекрывающиеся области памяти. Функция возвращает .

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

Теперь, в чем проблема с перекрывающейся памятью? Рассмотрим рис. 12.1.

Рис. 12.1. Перекрывающиеся копии

Целью является скопировать четыре экземпляра от до в участок от до . Здесь проблемой является , побайтовое копирование с перемещением в памяти из затрет до того, как он будет безопасно скопирован в ! (Может возникнуть также сценарий, когда копирование в памяти в обратном направлении разрушит перекрывающиеся данные.)

Функция была первоначальной функцией в System V API для копирования блоков памяти; ее поведение для перекрывающихся блоков памяти не была подробно определена тем или иным способом. Для стандарта С 1989 г. комитет почувствовал, что это отсутствие определенности является проблемой, поэтому они придумали . Для обратной совместимости была оставлена, причем поведение для перекрывающейся памяти было специально отмечено как неопределенное, а в качестве процедуры, корректно разрешающей проблемные случаи, была предложена .

Какую из них использовать в своем коде? Для библиотечной функции, которая не знает, какие области памяти ей передаются, следует использовать . Таким способом вы гарантируете, что не будет проблем с перекрывающимися областями. Для кода приложения, который «знает», что две области не перекрываются, можно безопасно использовать .

Как для , так и для (как и для ) буфер назначения является первым аргументом, а источник — вторым

Чтобы запомнить это, обратите внимание на порядок, который тот же самый, как в операторе присваивания:. (Справочные страницы во многих системах не помогают, предлагая прототип в виде » и полагаясь на то, что текст объяснит, что есть что

К счастью, справочная страница GNU/Linux использует более осмысленные имена.)

(Справочные страницы во многих системах не помогают, предлагая прототип в виде » и полагаясь на то, что текст объяснит, что есть что. К счастью, справочная страница GNU/Linux использует более осмысленные имена.)

Оглавление книги

memcpy_s

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

Поэтому ближе к концу 2009 г. компания Microsoft добавила memcpy(), CopyMemory() и RtlCopyMemory() в список функций, запрещённых в соответствии с методикой разработки безопасных программ Secure Development Lifecycle (SDL). Те разработчики, которые хотят создавать совместимые с SDL приложения, должны будут использовать вместо memcpy() функцию memcpy_s, позволяющую указывать размер буфера. Функция memcpy_s() непереносима и не включена в стандарт Си.

Алгоритм работы и реализации

memcpy() копирует содержимое src в буфер dst, например, так:

int i;

for( i = ; i < n; i++ )
   ((unsigned char*)dst);
 
return dst;

Но данный пример будет работать медленнее, чем любые практические реализации, так как они оптимизированы:

  • Не используют индексы, как в примере.
  • Перемещают за один цикл не один байт, а блок, равный машинному слову (2, 4 или 8 байт; 16, 32 или 64 бит), которое копируется процессором за то же время, что и байт. Такой подход наиболее эффективен при копировании данных, выровненных на границу машинного слова.
  • Иногда используют инструкции процессора для работы с блоками данных (movsX для i386) в этом случае функция пишется с применением языка ассемблера

Пример частично оптимизированной версии:

int i, m;
unsigned long  *wdst = dst;  // текущая позиция в буфере назначения
unsigned long  *wsrc = src;  // текущая позиция в источнике
unsigned char  *cdst, *csrc;
 
for(i = , m = n  sizeof(long); i < m; i++)  // копируем основную часть блоками по 4 или 8 байт
   *(wdst++) = *(wsrc++);                     // (в зависимости от платформы)
 
cdst = (unsigned char*)wdst;
csrc = (unsigned char*)wsrc;

for(i = , m = n % sizeof(long); i < m; i++)             // остаток копируем побайтно
   *(cdst++) = *(csrc++);
 
return dst;

Данная версия копирует 4 или 8 байт (размер типа long равен 32 битам на 32-битной платформе и 64 на 64-битной) за цикл, но не проверяет выровненность данных.

NOTES top

       Failure to observe the requirement that the memory areas do not
       overlap has been the source of significant bugs.  (POSIX and the C
       standards are explicit that employing memcpy() with overlapping areas
       produces undefined behavior.)  Most notably, in glibc 2.13 a
       performance optimization of memcpy() on some platforms (including
       x86-64) included changing the order in which bytes were copied from
       src to dest.

       This change revealed breakages in a number of applications that
       performed copying with overlapping areas.  Under the previous
       implementation, the order in which the bytes were copied had
       fortuitously hidden the bug, which was revealed when the copying
       order was reversed.  In glibc 2.14, a versioned symbol was added so
       that old binaries (i.e., those linked against glibc versions earlier
       than 2.14) employed a memcpy() implementation that safely handles the
       overlapping buffers case (by providing an "older" memcpy()
       implementation that was aliased to memmove(3)).

Определение

void *memcpy(void *dst, const void *src, size_t n);

где:

  • dst — адрес буфера назначения
  • srс — адрес источника
  • n — количество байт для копирования

Функция копирует n байт из области памяти, на которую указывает src, в область памяти, на которую указывает dst. Функция возвращает адрес назначения dst.

Области памяти не должны перекрываться (‖src−dst‖≥n){\displaystyle (\|src-dst\|\geq n)}, иначе данные могут быть скопированы неправильно, например таким образом:

 __src___
|        |
1234567890xxxxx
  |__   ___|
     dst

после копирования буфер dst содержит данные отличные от исходных, так как они были разрушены в процессе копирования:

   __dst___
  |        |
121212121212xxx

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

Для правильного копирования перекрывающихся областей нужно использовать функцию memmove(). Некоторые реализации memcpy() (например в libc FreeBSD и OpenBSD) делают то же что и memmove(), принуждая работать правильно даже неправильно написанную программу, однако при написании переносимой программы на это надеяться нельзя.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *