Производительность и быстродействие sed.

Прежде всего хочу отметить, что большинство sed скриптов вовсе не нуждаются в оптимизации по быстродействию: связано это с очень медленным чтением файлов с жёсткого диска. Например, ваш файл читается за 100 секунд, а sed-скрипт выполняется 2 секунды, таким образом, всё это вместе выполнится за 100 секунд (ага, не за 102, потому как чтение файла и обработку sed выполняет одновременно). Даже если мы и улучшим свой скрипт (например перепишем его на assembler'е), время работы не изменится, как было 100, так 100 и останется. Даже если скрипт сработает не за 2, а скажем за 0.2 секунды.

Многие будут спорить с такими тезисами - дескать они проверили, и у них получилось не так. Специально для них напомню про кеширование: сначала файл читается с диска в память, а потом он обрабатывается. Вот только из памяти его никто не стирает, у нас не маздай. Если запустить скрипт ещё раз, он не станет читать файл с диска, он прочитает его из памяти. Это займёт не 100 секунд, а 5. И при этом распараллелить процессы будет невозможно, потому общее время выполнения скрипта будет равно 5+2==7 секунд. Так-же будет если вы только-что создали тестовый файл (может он даже на диск не записался, а так и висит в памяти - в любом случае, он будет браться именно из памяти).

Проблема в том, что на практике скрипты выполняются именно за 100 секунд, а не за 7, как в тестах. Потому, к тестам надо относится очень подозрительно...

Время выполнения sed-скриптов.

Скрипты в sed выполняются в разы быстрее, чем в других скриптовых языках (на практике в 2-3 раза быстрее, а для bash - в 20-30). Это связано с тем, что все команды в sed-скриптах состоят из одной буквы, и наш процессор просто передаёт управление в нужный участок кода в зависимости от первого байта команды. Это намного быстрее, чем сначала выделять нужную команду из текста, а потом рассчитывать её хеш (как приходится делать в любых других языках). Тем не менее, мы с лёгкостью можем написать скрипт, который выполняется вечно, кроме того, многие задачи в sed не решаются обычным простым путём, и их приходится решать более медленными способами. Например: для выравнивания текста по правому краю с шириной строки N, нам достаточно добавить к строке слева N-n пробелов (n - длинна текущей строки). В sed так сделать не получается, и приходится сначала добавлять к строке слева N пробелов, а после этого, откусывать N символов справа (см. пример).

Очень сложно сказать, что будет работать быстрее на практике. Конечно, мы можем написать специализированную программу на ассемблере, и она отработает быстрее (может быть, а может быть и нет, выше я уже писал про скрипт, который выполняется за 0 секунд - по причине того, что он работает параллельно с заведомо более долгим процессом чтения с HDD), однако, нашу программу будет невозможно собрать на другом компьютере. Программа на С может и соберётся, но ведь её ещё и собирать надо! Кроме того, программа на С потребует компилятора, и прочих девелоперских штучек. Например необходимы будут библиотеки для работы с текстом, а их как раз и нет на подавляющем большинстве систем. Точнее есть, но уже собранные, а нам нужны их исходные тексты, или хотя-бы заголовочные файлы, что-бы заюзать уже собранные библиотеки. А их-то и нет. И опять бегинеры будут хаять нас, нашу программу и GNU в целом - дескать

Само собой, половина программ содержит ошибки прямо в исходниках из-за которых программа не просто не работает, она (!!!) не собирается. То есть, "грамотный программист-линуксоид" не осилил даже написать код без чисто языковых ошибок. Или, как вариант, у себя локально отключил проверки компилятором проверки несущественных с его точки зрения, ошибок (ну, там, прямое присвоение обычному указателю константного и подобного рода мелочи).

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

Время выполнения регулярных выражений в sed.

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

Однако, следует учитывать то, что строка просматривается хоть и один раз, но всегда целиком, т.е. выражение /^X/ будет просматриваться для всей строки, что конечно не слишком быстро если в строке 10 000 000 000 символов, к тому-же, это и не нужно.

Замечание

Если вы используете достаточно новую версию sed, то многие вещи там уже оптимизированы, к примеру посмотрите как это сделано в моей реализации sed (execute.c, см. функцию do_subst). Я использую версию 4.2. Впрочем большинство оптимизаций имеются и в намного более ранних версиях (например в версии 4.0.9 от 2004го года). Вообще говоря, лучше всё-же использовать самую новую версию (но НЕ бету!), там исправлены многие даже не ошибки, а скажем так - "неточности". К примеру, при попытке отредактировать терминал:
$ sed -i '' /dev/stdin
sed: couldn't edit /dev/stdin: is a terminal
Так пишет новая версия. Старая версия конечно тоже рухнет, вот только сообщение об ошибке будет понятно только после чтения моей книги (ну или внимательного изучения info sed и ковыряния исходных текстов)
$ sed-4.0.9 -i '' /dev/stdin
sed-4.0.9: Couldn't open temporary file /dev//sed1V5U24: Permission denied
А теперь представьте, что данная команда находится внутри скрипта, и вы получаете такое сообщение об ошибке. Гугл вам ничего не скажет (не, теперь скажет - выведет на эту страничку ;-) ), и никаких данных вы не сможете найти ни на сайте где вы взяли этот скрипт, ни даже лично от кодера, который этот скрипт написал... Конечно это мелочь, но именно такие мелочи очень сильно облегчают жизнь не только кодерам, но и даже пользователям, которым так-же приходится исполнять скрипты, а значит и разбираться с ошибками в них (в данном случае это конечно ошибка юзера, только идиот может попытаться отредактировать клавиатуру - она никак не редактируется и недоступна для записи (хотя рут может помигать на ней лампочками ;) )).

Оптимизация команды s.

В этой команде применена следующая оптимизация: первым делом ищется совпадение шаблона в строке, и если его нет, то никаких действий не происходит (работа команды s прерывается, и выполняется следующая команда скрипта). Следует учесть, что шаблон может присутствовать, но совпадения нет, например для строки "xxxZ" и выражения /xxx$/, хотя xxx и присутствует в строке, но совпадения нет. Кроме того, может нам нужно третье совпадение, а есть только первое и второе.

Оптимизация команды x.

А нет никакой оптимизации: в версии 4.2 (а так-же и в более ранних) создаётся временная строка, куда копируется буфер, затем область удержания копируется в буфер, и наконец временная строка копируется в область удержания. Я-же наивно полагал, что буфер становится областью удержания, а область удержания становится буфером. Почему не ввели эту очевидную оптимизацию - мне неизвестно, видимо разработчики посчитали, что будет быстрее использовать сложную строку в качестве буфера, и простую - в качестве области удержания (т.к. в области удержания можно выполнить всего три операции: копирование туда, копирование оттуда, и добавление к этой области). Тем не менее, структуры области удержания и буфера совершенно одинаковые уже много лет. Конечно эта оптимизация не очень и нужна: даже если в скрипте и применяется эта команда, то на её долю приходится очень незначительное количество времени.

Оптимизация команд d и D.

Данные команды призваны удалять строки. Однако они на самом деле ничего не удаляют: команда d попросту завершает работу всего скрипта, даже без обычного вывода буфера (потому, в примитивных скриптах вроде sed '7d' кажется, что выбранная строка удаляется). Такое поведение несколько затрудняет написание скриптов, в сложном случае необходимо учитывать прерывание - никакие команды скрипта не будут выполнятся после выполнения команды d. Это не хорошо и не плохо, просто об этом надо помнить, например:

#!/bin/sed -f
/XXX/{
	# если в строке есть XXX, то она не выводится, и работа скрипта прерывается
	d
}
# а эта часть скрипта выполняется только для строк, в которых XXX нету

Что касается команды D, то она работает по другому: Прежде всего производится поиск первого символа '\n' в буфере. Если такого символа нет, то команда работает в точности как команда d, а таких символов в строке никогда и не бывает, кроме случая, когда мы его сами туда вставили, командами s, N, G, или H (последняя команда тоже вставляет \n, но не туда, впрочем это потом всё равно извлекается в буфер). Если в буфере есть символ \n, то команда D работает совершенно по другому: она отрезает все символы в строке до первого \n и передаёт управление первой команде скрипта. При этом отрезание происходит просто сдвигом позиции начала строки, т.е. первая подстрока так и остаётся в памяти, хотя получить к ней доступ совершенно невозможно.

Однако эта "удалённая" первая подстрока недолго будет висеть в памяти: команда D обычно работает в паре с командой N (или с G или H), которая добавляет к буферу новую подстроку. На самом деле для буфера выделяется 50 байтов, потому для новой строки обычно хватает места (строчки обычно короче, а если строчки длиннее, то и буфер соответственно увеличивается). Однако рано или поздно наступает момент, когда грузить строчку некуда - и именно тогда, активные строчки сдвигаются в начало буфера, при этом затирая то, что было "удалённо" командой D. Возможен конечно и вариант, когда команда D очистила недостаточно место, и только в этом крайнем случае выполняется вызов функции REALLOC (точнее макроса), а уж работа этой функции зависит от конкретной OS.

Замечание

Работа макроса REALLOC сильно зависит не только от наличия свободной памяти, но и от операционной системы. Например в большинстве компьютеров под управлением Linux (а так-же *BSD) свободной памяти обычно нет вообще - вся память занята под разные буфера и кеши. Потому перед выделением новой области приходится сначала освободить доступную память. Естественно, это вовсе не говорит о том, что в виндовс что-то будет работать быстрее: просто в win нам придётся читать весь файл с диска, что в сотни раз медленнее, в Linux в подавляющем большинстве случаев наш файл уже будет лежать в одном из кешей, а вот маздай постирает нафиг его оттуда, чтоб всякие монстры вроде M$Word могли запустится за более-менее обозримое время, даже на устаревших компьютерах с 2Gb оперативной памяти на борту. (это не шутка и не опечатка, это довольно печальный факт).

Почему-же в ОС виндовс такой плохой менеджер памяти? Ответ очень прост: он не плохой, он просто очень старый. Сегодня программы занимают примерно столько-же места, что и раньше (нет, дистрибутивы программ со всякими рюшечками и картинками занимают в разы больше, но только дистрибутивы. В память грузится только основная часть программы и несколько основных рюшечек). К примеру, последняя версия sed занимает 99К, а старая версия (пятилетней давности) занимает 85К, при этом, общий объём памяти за последние 5 лет увеличился более чем в 10 раз! Ситуация с другими программами такая-же.

Потому, преимущества менеджера памяти Windows можно заметить только при запуске огромных программных комплексов, да ещё и только таких, которые запускаются самим пользователем, в ручную, и причём запускаются все целиком, в 58и окошках и пр. Например M$Office, или Firefox (особенно если там последняя сессия с 33я вкладками, причём во вкладках открыты сайты, написанные криворукими кодерами, которые совсем не оптимизированы (а даже криворукие кодеры обожают выносить свой кривой код на сторону клиента - она всегда бесплатная, в отличие от сервера)). Возможно, преимущества менеджера памяти виндовс были-бы заметны в программах, которые сначала грузят большой объём данных(хотя-бы 20% от RAM), а затем пишут его обратно на диск, но такие программы (например архиваторы с сжатием) всегда (насколько мне известно) выполняют настолько сложные действия, что копеечный выигрыш от распределителя памяти становится незаметным.

Это не удивительно: если действия простые, то их можно и нужно выполнять потоком, как например делает это sed, по той причине, что одна и так-же программа будет успешно выполнять действия и с файлом в 10К, и с файлом в 10Гб, независимо от объёма имеющейся памяти: вендовый блокнот - хороший пример, если в 9х он вообще не умел открывать большие файлы, то в NT5 это "исправили", просто убрали проверку - теперь большие файлы открываются часами, а если и откроются, то их невозможно отредактировать, информация в них молча и необратимо исчезает. (большие - сравнительно с объёмом ОЗУ, если у вас 2Гб, то файлы размером 200Мб для вас уже большие).

Пример типичного использования команд D и N.

Вы можете обсудить этот документ на форуме. Текст предоставляется по лицензии GNU Free Documentation License (Перевод лицензии GFDL).

Вы можете пожертвовать небольшую сумму яндекс-денег на счёт 41001666004238 для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.