Состояние функциональности "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" станет доступным...:)
Ответы
Ответ 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, хотя я еще не изучил этот код. Это довольно очевидное улучшение и компилятор с открытым исходным кодом. Кто-то создал патч.