Некоторое время назад я наткнулся на статью Александреску в DDJ 2001 года: http://www.ddj.com/cpp/184403799
Речь идет о сравнении различных способов инициализации буфера некоторым значением. Как то, что делает "memset" для однобайтовых значений. Он сравнил различные реализации (memcpy, явный цикл for, устройство duff) и не нашел лучшего кандидата для всех размеров наборов данных и всех компиляторов.
Цитировать:
There is a very deep, and sad, realization underlying all this. We are in 2001, the year of the Spatial Odyssey. (...) Just step out of the box and look at us — after 50 years, we're still not terribly good at filling and copying memory.
Обновлено: я работаю над программным обеспечением для обработки изображений. Мои элементы массива - это POD, и каждая миллисекунда на счету!
Изменить 2: Спасибо за первые ответы, вот дополнительная информация:





г) Примите тот факт, что попытка «трюков джедаев» с инициализацией приведет к большему количеству потерянных часов программиста, чем совокупная разница в миллисекундах между каким-то непонятным, но быстрым методом по сравнению с чем-то очевидным и ясным.
В статье DDJ признается, что memset - лучший ответ, и он намного быстрее, чем то, чего он пытался достичь:
There is something sacrosanct about C's memory manipulation functions memset, memcpy, and memcmp. They are likely to be highly optimized by the compiler vendor, to the extent that the compiler might detect calls to these functions and replace them with inline assembler instructions — this is the case with MSVC.
Итак, если у вас работает memset (то есть вы инициализируете одним байтом), используйте его.
Хотя каждая миллисекунда может считаться, вы должны установить, какой процент времени выполнения теряется на установку памяти. Скорее всего, это очень мало (1 или 2% ??), учитывая, что у вас также есть полезная работа. Учитывая, что усилия по оптимизации, вероятно, будут иметь гораздо лучшую доходность в другом месте.
При выполнении некоторых алгоритмов инициализация временного «вспомогательного» массива может составлять от 20% до 40% от общего времени выполнения. И мои данные часто многобайтовые, поэтому memset в этом случае не работает (я уже использую его для однобайтовых данных).
Ну, все зависит от вашей проблемной области и ваших спецификаций, сталкивались ли вы с проблемами производительности, не уложились в срок и определили memset как корень всего зла? Если это так, вы находитесь в единственном случае, когда вы могли бы подумать о настройке memset.
Тогда вы также должны иметь в виду, что набор мемов в любом случае будет зависеть от оборудования, на котором он работает, будет ли программное обеспечение работать на той же платформе в течение этих пяти лет? На той же архитектуре? Как только вы придете к такому выводу, вы можете попробовать «свернуть свой собственный» memset, обычно играя с выравниванием буферов, убедившись, что вы сразу обнуляете 32-битные значения в зависимости от того, что наиболее эффективно для вашей архитектуры.
Однажды я столкнулся с тем же для memcmpt, где накладные расходы на выравнивание вызывали некоторые проблемы, но обычно это не приводит к чудесам, только небольшое улучшение, если оно есть. Если вы упускаете свои требования из-за строгости, это не поможет вам продвинуться дальше.
Это зависит от того, что вы делаете. Если у вас очень конкретный случай, вы часто можете значительно превзойти системную libc (и / или встраивание компилятора) memset и memcpy.
Например, для программы, над которой я работаю, я написал выровненный по 16 байт memcpy и memset, предназначенный для небольших объемов данных. Memcpy был создан только для размеров, кратных 16, больше или равных 64 (с данными, выровненными по 16), а memset был создан только для размеров, кратных 128. Эти ограничения позволили мне получить огромную скорость, и, поскольку я контролировал приложение, я мог адаптировать функции именно к тому, что было необходимо, а также настроить приложение для согласования всех необходимых данных.
Скорость memcpy была примерно в 8-9 раз выше, чем у встроенной в Windows скорости memcpy, сокращая 460-байтовую копию до 50 тактовых циклов. Набор памяти был примерно в 2,5 раза быстрее, чрезвычайно быстро заполняя массив стека нулями.
Если вас интересуют эти функции, их можно найти здесь; опуститесь примерно до строки 600 для memcpy и memset. Они довольно банальны. Обратите внимание, что они предназначены для небольших буферов, которые должны находиться в кеше; если вы хотите инициализировать огромные объемы данных в памяти, минуя кеш, ваша проблема может быть более сложной.
Ура программному обеспечению с открытым исходным кодом! Жаль, что это GPL, я не могу отказаться от использования ее непосредственно в моем приложении :). Но большое спасибо за подтверждение того, что я думал о небольших наборах данных: вы можете превзойти систему memset / cpy. Теперь все, что мне нужно, это метод выбора метода, который будет использоваться для малых / средних / больших наборов данных.
Обратите внимание, что, хотя вы можете превзойти системный memset / cpy, 32-разрядная версия Windows на много медленнее, чем должна быть, особенно memcpy, которой удается копировать только около одного байта за такт. Я подозреваю, что новые библиотеки Windows лучше, как и Linux (хотя моя все еще превосходит их, хотя и не так сильно).
Windows не предоставляет memcpy, в отличие от вашего компилятора C.
Вам следует попробовать написать версию AVX для Sandy Bridge: en.wikipedia.org/wiki/Advanced_Vector_Extensions. Интересно, в какой момент ограничивающим фактором является пропускная способность памяти, а не пропускная способность процессора. Я полагаю, что даже если пропускная способность памяти ограничивает вашу пропускную способность memcpy, использование более эффективных инструкций даст вам больше циклов процессора, чтобы потратить в параллели с memcpy.
Если память не является проблемой, то предварительно создайте статический буфер нужного вам размера, инициализированный вашим значением (ями). Насколько мне известно, оба этих компилятора оптимизируют компиляторы, поэтому, если вы используете простой цикл for, компилятор должен сгенерировать оптимальные команды ассемблера для копирования буфера.
Если проблема с памятью, используйте буфер меньшего размера и скопируйте в новый буфер со смещениями sizeof (..).
HTH
Memset / memcpy в основном написаны с учетом базового набора инструкций, и поэтому могут уступать в производительности специализированным подпрограммам SSE, которые, с другой стороны, налагают определенные ограничения выравнивания.
Но чтобы свести его к списку:
Этот список подходит только для тех вещей, где вам нужна производительность. Слишком маленький / или однажды инициализированный набор данных не стоит хлопот.
Здесь - это реализация memcpy от AMD, я не могу найти статью, в которой описывается концепция, лежащая в основе кода.
Это очень информативно, спасибо. Однако меня беспокоят «границы», в которых вы решите использовать тот или иной метод. Они могут (и будут!) Различаться в зависимости от кеша и компилятора.
Ты прав. Цифры немного туманные. Но может служить индикатором. Для такого рода проблем не существует золотого правила.
Я бы всегда выбирал метод инициализации, который является частью среды выполнения или ОС (memset), которую я использую (в худшем случае выберите тот, который является частью библиотеки, которую я использую).
Почему: если вы реализуете свою собственную инициализацию, вы можете получить немного лучшее решение сейчас, но вполне вероятно, что через пару лет время выполнения улучшится. И вы не хотите выполнять ту же работу, что и ребята, обслуживающие среду выполнения.
Все это остается в силе, если улучшение времени выполнения незначительно. Если у вас есть разница на порядок между memset и вашей собственной инициализацией, тогда имеет смысл запустить ваш код, но я действительно сомневаюсь в этом случае.
Что ж ... Я склонен согласиться с вами, но "Dark Shikari" опубликовал интересные цифры о том, как он побил memset / cpy для небольших массивов нетривиальными суммами.
Вы можете взглянуть на liboil, они (пытаются) предоставить разные реализации одной и той же функции и выбрать самую быструю при инициализации. Liboil имеет довольно либеральную лицензию, так что вы можете использовать ее также для проприетарного программного обеспечения.
http://liboil.freedesktop.org/
Спасибо, выглядит очень интересно. Я обязательно там долго посмотрю. А вы случайно не знаете, собирается ли он непосредственно под MS Visual C++?
Извините, я понятия не имею. Может быть, вы можете спросить в списке рассылки, некоторые из последних коммитов выглядят так, как будто они исправили некоторую проблему в Windows.
У Форум MASM есть много невероятных программистов / любителей на ассемблере, которые до смерти решили эту проблему (посмотрите The Laboratory). Результаты были очень похожи на ответ Кристофера: SSE невероятен для больших, выровненных буферов, но, опустившись, вы в конечном итоге достигнете такого маленького размера, что базовый цикл for будет таким же быстрым.
Не могли бы вы дать глубокую ссылку на конкретную тему форума о memset?
Если вам нужно выделить свою память, а также инициализировать ее, я бы:
Причина этого в том, что calloc инициализирует память нулями за вас. Хотя это повлечет за собой накладные расходы на обнуление памяти, большинство компиляторов, вероятно, будут иметь эту процедуру в высокой степени оптимизированной - более оптимизированной, чем malloc / new с вызовом memcpy.
Именно поэтому я упомянул, что нужно изменить как можно больше значений по умолчанию на ноль. Сравните memset (...): он также не работает, если все байты структуры не имеют одинакового значения.
В некоторых операционных системах для выделения памяти нулю требуется всего одна операция памяти на страницу, поскольку дескрипторы страниц могут иметь «нулевой» флаг.
Как всегда с такими вопросами, проблема ограничена факторами, не зависящими от вас, а именно пропускной способностью памяти. И если ОС хоста решает начать подкачку памяти, все становится намного хуже. На платформах Win32 память выгружается, и страницы выделяются только при первом использовании, что создает большую паузу на каждой границе страницы, пока ОС находит страницу для использования (для этого может потребоваться страница другого процесса, которая будет выгружена на диск).
Однако это самый быстрый из когда-либо написанных memset:
void memset (void *memory, size_t size, byte value)
{
}
Не делать чего-либо - всегда самый быстрый способ. Можно ли как-нибудь написать алгоритмы, чтобы избежать начального memset? Какие алгоритмы вы используете?
«(...) проблема ограничена факторами, не зависящими от вас (...)» Действительно. Как теперь выбрать лучшее решение? Разумно ориентированным на будущее способом?
Если у вас нет хрустального шара, вы не сможете. Единственный вариант для будущей проверки - использовать все, что предоставляет ОС, либо собственные вызовы API, либо динамически подключаемые библиотеки времени выполнения. Таким образом, управление памятью ОС будет оптимизировано для системы, в которой работает ОС (надеюсь).
Сейчас уже не 2001 год. С тех пор появились новые версии Visual Studio. Я потратил время на изучение мемсета в них. Они будут использовать SSE для memset (если, конечно, есть). Если ваш старый код был правильным, статистически теперь будет быстрее. Но вы можете попасть в неудачный угол. Я ожидаю того же от GCC, хотя код еще не изучал. Это довольно очевидное улучшение и компилятор с открытым исходным кодом. Кто-то создаст патч.
Вероятно, вы правы, но учтите, что проблема выходит за рамки memset, который нельзя использовать для многобайтовых значений.
К сожалению, очевидное и ясное не ускоряет работу моего программного обеспечения. Я обычно довольно консервативен, как и вы, очевидно, но это одна из тех частей, где я чувствую, что могу найти лучший компромисс.