Состояние функциональности "memset" в C ++ с современными компиляторами - PullRequest
19 голосов
/ 05 октября 2008

Контекст:

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

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

Цитата:

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

Вопрос:

  1. Кто-нибудь имеет более свежую информацию об этой проблеме? Действительно ли последние реализации GCC и Visual C ++ работают значительно лучше, чем 7 лет назад?
  2. Я пишу код со сроком жизни 5+ (возможно, более 10) лет, который будет обрабатывать размеры массивов от нескольких байтов до сотен мегабайт. Я не могу предположить, что мой выбор сейчас будет оптимальным через 5 лет. Что я должен делать:
    • а) использовать системный набор настроек (или эквивалентный) и забыть об оптимальной производительности или предположить, что среда выполнения и компилятор справятся с этим для меня.
    • b) раз и навсегда выполнить сравнительный анализ для различных размеров массивов и компиляторов и переключаться во время выполнения между несколькими подпрограммами.
    • в) запустить эталонный тест при инициализации программы и переключаться во время выполнения на основе точных (?) Данных.

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

Редактировать 2: Спасибо за первые ответы, вот некоторые дополнительные сведения:

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

Ответы [ 12 ]

10 голосов
/ 05 октября 2008

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

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

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

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

8 голосов
/ 05 октября 2008

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

5 голосов
/ 05 октября 2008

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

Но чтобы уменьшить его до списка:

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

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

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

4 голосов
/ 05 октября 2008

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

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

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

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

4 голосов
/ 05 октября 2008

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

2 голосов
/ 05 октября 2008

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

http://liboil.freedesktop.org/

1 голос
/ 06 октября 2008

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

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

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

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

1 голос
/ 06 октября 2008

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

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

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

1 голос
/ 05 октября 2008

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

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

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

1 голос
/ 05 октября 2008

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

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

НТН

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