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

Состояние функциональности "memset" на С++ с современными компиляторами

Контекст:

Некоторое время назад я наткнулся на эту статью 2001 DDJ от Alexandrescu: http://www.ddj.com/cpp/184403799

Это о сравнении различных способов инициализации буфера для некоторого значения. Как то, что "memset" делает для однобайтовых значений. Он сравнивал различные реализации (memcpy, explicit "for" loop, duff device) и не нашел лучшего кандидата во всех размерах данных и всех компиляторах.

Цитата:

Существует очень глубокая и печальная реализация, лежащая в основе всего этого. Мы в 2001 году, год Пространственной Одиссеи. (...) Просто выходите из окна и смотрите на нас - через 50 лет мы все еще не очень хорошо умеем заполнять и копировать память.

Вопрос:

  • У кого-нибудь есть более свежие сведения об этой проблеме? Реализуются ли последние реализации GCC и Visual С++ значительно лучше, чем 7 лет назад?
  • Я пишу код, который имеет продолжительность жизни 5+ (вероятно, 10+) лет и будет обрабатывать размеры массивов от нескольких байтов до сотни мегабайт. Я не могу предположить, что мой выбор теперь будет оптимальным через 5 лет. Что мне делать:
    •  
    • a) используйте системный memset (или эквивалент) и забудьте об оптимальной производительности или предположите, что среда выполнения и компилятор будут обрабатывать это для меня.  
    • b) анализировать раз и навсегда различные размеры и компиляторы массивов и переключаться во время выполнения между несколькими процедурами.  
    • c) запустить тест при инициализации программы и переключиться во время выполнения на основе точных (?) данных.

Изменить: Я работаю над программным обеспечением для обработки изображений. Мои элементы массива - это POD, и каждый миллисекунды рассчитывает!

Изменить 2: Спасибо за первые ответы, вот несколько дополнительных сведений:

  • Инициализация буфера может составлять 20% -40% от общей продолжительности выполнения некоторых алгоритмов.
  • Платформа может варьироваться в течение следующих 5+ лет, хотя она останется в "самой быстрой цене ЦП, которую можно купить у DELL". Компиляторы будут в какой-то форме GCC и Visual С++. Никаких встроенных материалов или экзотических архитектур на радаре
  • Я бы хотел услышать от людей, которые должны были обновить свое программное обеспечение, когда появились MMX и SSE, так как мне придется делать то же самое, когда "SSE2015" станет доступным...:)
4b9b3361

Ответ 1

В статье DDJ признается, что memset - лучший ответ и намного быстрее, чем он пытался достичь:

Есть что-то неприкосновенное Функции управления памятью C memset, memcpy и memcmp. Они есть вероятно, будут сильно оптимизированы поставщика компилятора, в той степени, в которой компилятор может обнаруживать вызовы эти функции и заменить их на встроенные инструкции ассемблера - это в случае с MSVC.

Итак, если memset работает для вас (т.е. вы инициализируетесь одним байтом), используйте его.

В то время как каждая миллисекунда может рассчитывать, вы должны установить, какой процент вашего времени выполнения теряется для установки памяти. Вероятно, очень низкий (1 или 2%?), Учитывая, что у вас есть полезная работа. Учитывая, что усилия по оптимизации, вероятно, будут иметь гораздо лучшую норму прибыли в другом месте.

Ответ 2

Форум MASM имеет множество невероятных ассемблеров-программистов/любителей, которые полностью избили эту проблему до конца (см. Laboratory). Результаты были очень похожи на ответ Кристофера: SSE невероятно подходит для больших, выровненных, буферов, но, опустившись, вы, в конечном счете, достигнете такого небольшого размера, что базовый цикл for будет таким же быстрым.

Ответ 3

Memset/memcpy в основном написаны с учетом набора основных инструкций и поэтому могут быть превзойдены специализированными подпрограммами SSE, которые, с другой стороны, обеспечивают определенные ограничения выравнивания.

Но чтобы свести его к списку:

  • Для наборов данных <= несколько сотен килобайт memcpy/memset выполняется быстрее, чем все, что вы могли бы макетировать.
  • Для наборов данных > мегабайты используют комбинацию memcpy/memset для получения выравнивания, а затем используют собственные оптимизированные подпрограммы SSE/резервные копии для оптимизированных подпрограмм от Intel и т.д.
  • Обеспечьте выравнивание при запуске и используйте собственные SSE-процедуры.

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

Здесь - это реализация memcpy от AMD, я не могу найти статью, в которой описывается концепция кода.

Ответ 4

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

Ответ 5

Это зависит от того, что вы делаете. Если у вас очень специфический случай, вы часто можете значительно превзойти системный libc (и/или компилятор) в memset и memcpy.

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

Memcpy выполнил примерно в 8-9 раз скорость родной memcpy Windows, набрав 460-байтовую копию до всего 50 тактов. Мемсет был примерно в 2,5 раза быстрее, очень быстро заполнив массив стеков нулей.

Если вас интересуют эти функции, их можно найти здесь; оставьте около 600 строк для memcpy и memset. Они довольно тривиальны. Обратите внимание, что они предназначены для небольших буферов, которые должны быть в кеше; если вы хотите инициализировать огромное количество данных в памяти при обходе кеша, ваша проблема может быть более сложной.

Ответ 6

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

http://liboil.freedesktop.org/

Ответ 7

Хорошо, это зависит от вашей проблемной области и ваших спецификаций, вы столкнулись с проблемами производительности, не смогли выполнить сроки, установленные сроками, и определили memset как корень всего зла? Если это так, вы находитесь в одном и единственном случае, когда вы можете рассмотреть некоторые настройки memset.

Тогда вы также должны помнить, что memset все равно будет меняться на аппаратном обеспечении, на котором он запущен, в течение этих пяти лет будет ли программное обеспечение работать на одной платформе? На той же архитектуре? Вы пришли к такому выводу, что можете попробовать "сворачивать свой собственный" memset, обычно играя с выравниванием буферов, следя за тем, чтобы вы сразу равнялись 32-битным значениям в зависимости от того, что наиболее эффективно для вашей архитектуры.

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

Ответ 8

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

Если проблема с памятью, используйте меньший буфер и скопируйте его в значениях sizeof (..) в новый буфер.

НТН

Ответ 9

Я бы всегда выбирал метод инициализации, который является частью среды выполнения (OS) (memset), которую я использую (худший случай выбирает тот, который является частью библиотеки, которую я использую).

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

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

Ответ 10

Если вам нужно выделить свою память и инициализировать ее, я бы:

  • Использовать calloc вместо malloc
  • Измените как можно больше значений по умолчанию, равных нулю (например: пусть значение по умолчанию для нулевого значения равно нулю или значение по умолчанию для логической переменной равно "true", сохраните обратное значение в структуре)

Причиной этого является то, что calloc zero инициализирует память для вас. Хотя это будет связано с накладными расходами для обнуления памяти, большинство компиляторов, скорее всего, будут иметь эту оптимизацию с высокой степенью оптимизации - более оптимизировано это malloc/new с вызовом memcpy.

Ответ 11

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

Это, однако, абсолютный самый быстрый memset когда-либо написанный:

void memset (void *memory, size_t size, byte value)
{
}

Не делать что-то всегда самое быстрое. Можно ли записать алгоритмы, чтобы избежать начального memset? Какие алгоритмы вы используете?

Ответ 12

Год не 2001. С тех пор появились новые версии Visual Studio. Я нашел время, чтобы изучить memset в них. Они будут использовать SSE для memset (если доступно, конечно). Если ваш старый код был правильным, статистически, если теперь будет быстрее. Но вы можете попасть в неудачный треугольник. Я ожидаю того же от GCC, хотя я еще не изучил этот код. Это довольно очевидное улучшение и компилятор с открытым исходным кодом. Кто-то создал патч.