четверг, 7 февраля 2013 г.

управляющие символы коды

Matcher m = KILL_CC_PATTERN.matcher(s);

public static final Pattern KILL_CC_PATTERN = Pattern.compile("[\\x00-\\x1F]");

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

Нет, не дает. Всё те же 520000a3%.

s = s.replaceAll("[\\x00-\\x1F]", "");

Вспоминаем, что же именно у нас за строчки и что нам нужно фильтровать и что такое \p{Cntrl}. Понимаем, что этот элемент по сути равнозначен выбору символов с кодами от 0 до 31 включительно, плюс 127. На всякий случай быстро проверяем, а не дает ли что-то, если мы поменяем этот \p{Cntrl} на более банальное перечисление всех символов в регулярном выражении через оператор типа [a-z], в нашем случае:

Делаем на скорую руку простенькую обвязку, замеряющую время работы алгоритма, изолируем его от всего остального кода, генерируем тестовую входную строчку, которую будем прогонять миллионы раз через алгоритм и замеряем производительность. Получается, что первый вариант обрабатывает 517009 строчек в секунду. Делаем несколько замеров, понимаем, что точность наших измерений и экспериментов порядка 2-3% т.е. грубо говоря, на последние 4 цифры можно совсем не смотреть, а на пятую цифру с конца смотреть, но не заглядываться. Т.е. результаты где-то между 500..540 тысячами.

Я даже знаю, быстрый поиск в Google по фразе «java strip non-printable characters» выдает именно этот вариант. Итак, задача понятна, цели понятны, программистское самолюбие задето («как же так, неужели я не смогу разогнать эту несчастную строчку»), поехали!

s = s.replaceAll("\\p{Cntrl}", "");

С моей точки зрения логично было бы, чтобы такая задача упиралась в диск или сеть нагрузка на процессор здесь должна быть минимальная. Однако, простейший профайлинг показал совсем другое. Исходный вариант содержал строчку, на которую тратилось 80-90% рабочего времени алгоритма это была ровно одна строчка, которая и делала операцию 3, вот она:

Передать их дальше (записать на диск или отправить по сети)

Очистить все строки в (out1, out2, ..., outm) от управляющих символов

Сделать с десяток простейших операций типа копирования in в out с проверками простых условий

Получить извне (по сети или с диска) набор из N полей: (in1, in2, ..., inn)

По сути вся деятельность алгоритма сводилась к:

Оказалось, что эта операция совсем не такая «простейшая», как кажется, особенно если рассматривать её в современных языках с виртуальной машиной. Чуть ниже я покажу, как можно заменить решение в одну строчку на решение в пару десятков строчек, увеличив производительность алгоритма в ~10 раз. Сразу оговорюсь, что примеры будут относится к Java, но аналогичные рассуждения будут справедливы и для большинства других языков и виртуальных машин в первую очередь, для .NET-based.

Получилось так, что мне довелось оптимизировать код кластерной задачи, которая входила в состав Большого Кластерного Алгоритма и занималась весьма простой вещью: входной поток из n полей нужно было в зависимости от содержимого полей переразложить в выходной поток из m полей и почти успокоиться. Почти потому что внутри полей были строчки произвольного вида, которые нужно было «очистить» провести простейшую, казалось бы, операцию удаления всех управляющих символов из строки.

Как убрать все управляющие символы из строки история одной бурной оптимизации

24 августа 2011 в 09:00

Как убрать все управляющие символы из строки история одной бурной оптимизации / Хабрахабр

Комментариев нет:

Отправить комментарий