Сложность функции memset в C

Я обсуждал с некоторыми друзьями кусок кода, и мы обсуждали использование функции memset в C, которая является порядком в нотации Big-O для этой функции, если мы инициализируем массив размера N?

4 ответов


в системе, где у вас есть прямой доступ к таблицам страниц и они хранятся в иерархическом порядке, memset может быть реализован в O(log n) путем замены всего виртуального сопоставления адресов ссылками на копирование при записи на одну страницу, заполненную заданным значением байта. Обратите внимание, однако, что если вы собираетесь делать какие-либо будущие изменения объекта, нормальный O(n) стоимостью memset будет просто отложено до ошибки страницы, чтобы создать отдельные копии страниц, когда они модифицированный.


вы спросили о сложности, но вы, вероятно, намеревались спросить о производительности.

сложность, упомянутая с обозначением O (n), - это концепция, касающаяся того, как число операций в алгоритме вынуждено расти по мере увеличения размера задачи. O (n) означает, что должно быть выполнено некоторое количество шагов, пропорциональных размеру входного сигнала. Она не говорит, какова эта пропорция. memset-O (n). O (n2) означает некоторое количество шагов, пропорциональных n2 должна быть выполнена. memset не O (n2) потому что установка 2n байт занимает только в два раза больше работы, чем n байт, а не в четыре раза больше работы, в целом.

вас, скорее всего, больше интересует производительность memset, потому что версия библиотеки memset работает намного быстрее, чем версия C, которую вы могли бы написать.

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

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

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


сложность равна O (n). Это основное.


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

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