УДК681.326:519.713
МОДИФИКАЦИЯ МЕТОДА МИНИМИЗАЦИИ БУЛЕВЫХ ФУНКЦИЙ ДЛЯ МУЛЬТИЯДЕРНОЙ INTEL-АРХИТЕКТУРЫ
МИХТОНЮК С.В., ДАВЫДОВ М.Д.,
КУЗНЕЦОВ Е.С., ПАРФЕНТИЙ А.Н.____________
Описывается квазиоптимальный метод минимизации булевых функций, ориентированный на мультиядерную архитектуру Intel, и его программная реализация, предназначенная для существенного уменьшения аппаратурных и временных затрат при имплементации частично определенных булевых функций от большого количества переменных.
1. Введение
Современные цифровые и аналоговые устройства, начиная от бытовых приборов и заканчивая суперкомпьютерами, представляют собой совокупность блоков и модулей, каждый из которых реализует определенную функцию. Неотъемлемым компонентом технического обеспечения автоматизированных систем различного назначения являются микропроцессоры. Именно от эффективности их работы зависит производительность системы в целом. Различают универсальные и специализированные микропроцессоры, предназначенные для выполнения особых функций в реальном масштабе времени. Современные процессорные элементы содержат десятки миллионов транзисторов, при имплементации которых в кристалл возникает проблема поддержки баланса между производительностью и затратами электроэнергии, а также обеспечения внутреннего параллелизма на уровне инструкций. Решение данной проблемы возможно лишь при использовании современных методов и средств автоматизированного проектирования, позволяющих выполнить «кремниевую компиляцию» - преобразование функционального описания устройства на языке высокого уровня в топологические чертежи.
Однако конкурентоспособность проекта определяется не только эффективностью и производительностью устройства. Большое значение имеет также время выхода на рынок готового изделия. Наиболее длительным среди всех этапов жизненного цикла изделия является проектирование устройства. На начальном этапе проектирования используются стандартные формализованные приемы синтеза комбинационных схем и цифровых автоматов на интегральных элементах. Однако непрерывный рост степени интеграции элементов, расширение их функциональности и необходимость программной перенастройки в процессе эксплуатации приводят к ограничению области применения формализованных методов синтеза схем. Тем не менее, существуют классические приемы проектирования «схемной логики», которые могут быть применены для разработки автоматизированных систем
любого класса. К ним относятся методы минимизации булевых функций, рассматриваемые в данной работе. Наибольшую степень минимизации дает полный перебор возможных вариантов решения, но это требует длительного времени для получения результата. За это время изделие может морально устареть еще до выхода на рынок. Поэтому разработка новых и совершенствование существующих методов минимизации булевых функций для мультиядерной архитектуры является актуальной задачей.
Цель исследования - существенное снижение временных и аппаратурных затрат на минимизацию булевых функций от большого числа переменных путем модификации существующего метода минимизации для мультиядерной архитектуры Intel.
Для достижения данной цели необходимо решить следующие задачи: 1) модифицировать метод Квайна-Мак-Класки [1] для более эффективного выполнения поставленной задачи и приспособить его для программной реализации; 2) разработать форматы данных для внутреннего представления хранимой инфор -мации; 3) реализовать последовательную версию программы минимизации; 4) модифицировать полученную систему, позволив всем, не связанным между собой, вычислениям идти параллельно.
Объект исследования - методы минимизации булевых функций, используемых при проектировании цифровых систем на кристаллах.
Предмет исследования - не полностью определенные булевы функции, заданные таблицей истинности или булевыми уравнениями.
2. Анализ существующих методов минимизации
В настоящее время существует большое многообразие методов минимизации булевых функций, среди которых наиболее популярными являются: карты Карно [1], методы перебора [1], Квайна-Мак-Класки [1,5], граф-метод [10], Espresso [2-4].
Метод карт Карно является одним из наиболее сложных в программной реализации методов и не приспособлен для работ с количеством переменных более шести, ввиду чего он не может эффективно использоваться для минимизации функций, применяемых в проектировании.
Перебор позволяет найти наилучшее решение, но он также не эффективен, так как время выполнения минимизации многократно возрастает с увеличением количества переменных.
Наиболее технологичным является метод Квайна-Мак-Класки, предназначенный для минимизации не полностью определенных булевых функций от большого числа переменных. Однако он очень критичен к аппаратным ресурсам и уступает в скорости методу Espresso. Последний наиболее часто применяется для минимизации функций от большого числа переменных. Метод Espresso не всегда дает минимальный
50
РИ, 2007, № 3
вариант функции, однако его результат является очень близко аппроксимированной минимизацией. В работе предлагается система минимизации булевых функций Essential Variables, в которой реализованы модифицированный метод Квайна-Мак-Класки, позволяющий значительно уменьшить время выполнения процедуры минимизации функций от большого числа переменных, и оптимизация вычислений путем их распараллеливания на основе многоядерной архитектуры Intel.
3. Модифицированный метод Квайна-Мак-Класки
Классический метод Квайна-Мак-Класки предполагает выполнение следующих действий для получения минимальной ДНФ:
1. Составление таблицы исходных термов ДНФ.
2. Заполнение таблицы импликант.
3. Заполнение и сокращение матрицы покрытия.
4. Выделение ядер.
5. Выбор наименьшего покрытия.
Метод Квайна-Мак-Класки в стандартном виде обрабатывает только полностью заданные булевы функции, обладает невысоким быстродействием и требует значительных ресурсов.
В предлагаемом методе существенных переменных используется новый подход к формированию и построению таблицы импликант. Метод оперирует единичными и нулевыми наборами таблицы истинности, которые однозначно задают булеву функцию [6].
Последовательность вычислений приведена ниже.
1. Построение таблицы существенных переменных.
2. Заполнение списка термов ДНФ.
3. Заполнение матрицы импликант и определение покрытий.
4. Минимизация матрицы импликант.
5. Выбор минимального суммарного покрытия и получение минимизированной функции в форме ДНФ.
На первом этапе формируется таблица существенных переменных, пример заполнения которой представлен в табл. 1.
При определении элемента таблицы ajj = Ti © Fj учитывается показатель инверсии Ijj = Tj © ajj, где i = 1,M, j = 1, N, T - множество единичных наборов, F - множество нулевых наборов, M - количество единичных наборов, N - количество нулевых наборов.
После выполнения данной операции ячейка aij таблицы существенных переменных содержит последовательность битов, которые несут информацию о вхождении переменных в терм, а элемент Iij содержит информацию об инверсии переменных в терме.
Таблица 1. Таблица существенных переменных
00001 00100 01001 01110
00101 3 5 2 3 2 4 5
01000 2 5 2 3 5 3 4
01101 2 3 2 5 3 4 5
10100 I 3 5 1 12 3 5 12 4
10101 1 3 1 5 1 2 3 12 4 5
10111 13 4 14 5 12 3 4 12 5
11000 12 5 12 3 1 5 13 4
11010 12 4 5 12 3 4 14 5 1 3
11101 12 3 12 5 13 14 5
11111 12 3 4 12 4 5 13 4 15
13 5 13 5 3 5 1 4
3 5 12 5 13 5 1 4
DNF 2 3 12 5
12 3
12 3
Далее на основе полученной таблицы по столбцам находятся термы ДНФ, полностью покрывающие единичные наборы. Все полученные термы вносятся в список ДНФ, откуда удаляются повторяющиеся элементы. Данная операция выполняется параллельно для каждого столбца на основе разработанного рекурсивного алгоритма. При этом находят и включают в терм переменные, способные покрыть наибольшее число наборов. Если существует несколько переменных с одинаковым количеством покрываемых наборов, то находят отдельные альтернативные ДНФ для каждого из них.
Следующим шагом минимизации является формирование импликантной матрицы. Для каждого ДНФ проверяют, какие столбцы таблицы можно покрыть с его помощью. При этом определяются «ядра» - ДНФ наборы, покрывающие столбцы, не покрываемые другими ДНФ наборами. Они включаются в результирующую формулу. Данный шаг также выполняется параллельно.
Далее проверяются перекрытия одними элементами других, что позволяет выявить необязательные наборы и удалить их из результата [5].
Для рассмотренного примера импликантная матрица будет иметь вид, представленный в табл. 2.
Т аблица 2. Импликантная матрица
00001 00100 01001 01110
13 5 + +
1 4 +
13 5 + +
3 5 + +
12 5 +
12 3 +
2 3 +
РИ, 2007, № 3
51
Результатом, полученным с помощью данного метода, является тупиковая (неизбыточная) функция, которая полностью задает все состояния цифрового устройства, имея при этом квазиоптимальную структуру.
Для рассмотренного примера вариантов ответа может быть несколько:
У1 = X1X3X5 v X1X3X5-У2 = X3X5 v X1X2X5 v X1X4;
У3 = X3X5 v X1X3X5-
Среди них минимальной является следующая функция:
y = X3X5 V X1X3X5;
Q = 10,
Значения, равные единице и нулю, показывают, присутствует ли переменная в наборе или нет, а позиция в массиве определяет номер переменной. Данный способ представления информации значительно упрощает ввод и обработку данных на этапе вычисления сумм по модулю 2 [6]. Однако при этом возникает про блема адресации памяти.
Известно, что наименьшим адресуемым элементом памяти является байт. Адресация отдельных битов невозможна (из поддерживаемых современными системами программирования встроенных типов данных наименьший размер в 1 байт имеет булева переменная). Использование обычных массивов для хранения указанных переменных приводит к восьмикратному перерасходу памяти.
где Q - оценка аппаратной реализации проекта или функция по Квайну, вычисляемая по формуле
Q
n n
= Nt +Z Vi +Z Ii-
i=1 i=1 ’
(1)
здесь Nt - количество термов; 2 Vi - количество
i=1
переменных, 2 Ii — количество инверсий. i=1
Учитывая, что имплементация булевых функций в кристаллы PLD (Programmable Logic Device) не зависит от количества инверсий, формула (1) может быть упрощена к следующему виду:
Q = Nt +ZVi.
i=1
4. Внутренние структуры данных
Одним из важнейших факторов, влияющих на производительность приложения, является структура внутренних данных. При ее проектировании необходимо найти баланс между скоростью обработки информации и размером структуры [9].
В 32-битных системах наиболее быстро обрабатываются 32-битные данные, так как доступ к частичным регистрам требует значительного времени. Для большинства переменных, таких как, например, флаги состояний указанный формат не является оптимальным. Использование для флага 32 битов не гарантирует высокой производительности, поскольку существуют проблемы использования кэш-памяти и ее страниц.
Рассматриваемый в данной работе метод ориентирован на использование функций большого числа переменных, поэтому особое значение имеет компактность структур данных. На этапе проектирования автоматизированной системы входные наборы представляются в виде массивов двоичных чисел, длина которых равна количеству переменных:
X1 X4 [Ху
1 0 0 1 1 = Х1Х4Х5
Для решения указанной проблемы предлагается упакованный формат, реализованный с помощью класса Bitset. Он позволяет хранить отдельные биты в массиве 32-битных переменных. Память под массив выделяется динамически, что позволяет, в отличие от стандартных форматов [7], определять размер структуры не на этапе компиляции, а во время исполнения программы.
Выделение отдельного бита имеет константную амортизированную сложность и реализуется с помощью операции побитового «И» с маской. Маска формируется несколькими операциями побитового «И» и сдвигами. На формирование маски и на одну операцию в среднем требуется около десяти тактов, что является небольшой платой за значительную экономию памяти.
Для реализации класса необходим набор конструкторов, поддерживающих копирование объекта, конструирование его из символьной строки и целочисленных переменных, а также множество допустимых арифметических и логических операторов.
Тестирование описанной выше структуры данных с помощью средства VTune Performance Analyzer фирмы Intel [9] позволяет выявить наиболее критичные ко времени функции, на которых следует сосредоточить дальнейшие усилия по оптимизации. В целях дополнительного увеличения производительности при реализации отдельных функций целесообразно использовать язык ассемблера, в операциях сложения следует применять специальные операторы, упрощающие анализ флага переноса между блоками битов.
ДНФ представляется массивом объектов расширенного варианта класса Bitset, в котором хранится маска, указывающая на переменные с отрицанием:
X]_______х4 х5
1 0 0 1 1
— —
При заполнении таблицы для уменьшения временных затрат на копирование элементов следует использовать специальную операцию суммирования по модулю 2, которая может быть реализована в виде конструктора.
52
РИ, 2007, № 3
Использование предложенного нового способа представления данных при проектировании цифровых систем повышенной сложности ограничивается объемом доступной памяти. Так, для хранения таблицы сумм по модулю 2 при использовании функции 100 переменных, заданной на 3000 единичных и 3000 нулевых наборах, требуется около двух гигабайтов памяти, что приводит к активному использованию файла подкачки операционной системы и нерациональному использованию процессорного времени. Для устранения указанного недостатка необходимо отказаться от хранения таблицы и выполнять вычисления только по одному столбцу во время заполнения ДНФ, что позволит многократно увеличить предельное число переменных и наборов в обрабатываемой функции.
Разработанные внутренние структуры данных являются универсальными и имеют высокий уровень пригодности к повторному использованию.
5. Распараллеливание вычислений
Эффект применения многопоточных технологий для реализации приложений становится все более ощутимым для пользователя в последнее время при появлении на рынке мультиядерных архитектур от Intel. Ранее параллельная обработка данных проводилась лишь на специализированных компьютерах либо в параллельных вычислительных сетях. Обычный же компьютер ее только эмулировал с помощью системы переключения потоков из параллельного виртуального режима в фактически последовательные вычисления. Покупая систему с двуядерным процессором, пользователь ожидает, что любое приложение будет работать в два раза быстрее, однако фактически наблюдается лишь незначительный прирост производительности. Это объясняется тем, что в любом приложении присутствует зависимость по данным [8,9], которые могут быть одновременно входными и выходными, но на различных этапах вычислений. Такие процедуры изначально являются последовательными и заведомо не могут выполняться одновременно. Распараллелить можно лишь не связанные между собой вычисления внутри таких блоков (рис. 1).
Block 1 Block 2
Рис. 1. Распараллеливание при наличии зависимости по данным между двумя блоками
Операционная система не может сама определить, где присутствует или отсутствует зависимость между данными, поэтому запускает все приложения в одном потоке. Единственным, кто знает эти зависимости в алгоритме, является программист и он может указать операционной системе, какие из вычислений можно выполнять параллельно.
РИ, 2007, № 3
Разр аботанный метод минимизации содержит множе -ство возможностей для параллельного выполнения различных этапов вычислений. Большинство операций в методе имеют циклический характер, причем данные на различных итерациях не связаны между собой. На этапе проектирования необходимо вводить в такие циклы максимально возможное количество вычислений. Так, например, при формировании таблицы сумм по модулю 2 необходимо включить все вычисления, связанные с определением нулевых и единичных наборов, в один цикл нахождения ДНФ, что позволит сократить накладные расходы на создание потоков, так как они в этом случае создаются лишь один раз для обеих операций.
В целях обеспечения возможности распараллеливания вычислений разработана надстройка для классов, позволяющая запустить любой из методов в нескольких потоках. Единственным ограничением при этом является необходимость соблюдения установленной структуры прототипа: каждому методу в качестве первого параметра неявно передается указатель на объект класса, для которого этот метод был вызван, пар аметры принимаются в поток через заданную структуру данных [8]. Это автоматически делает невозможным использование в методах стандартной API функции CreateThread(). В целях устранения данного недостатка необходимо ввести в класс дополнительную статическую функцию, предназначенную для вызова в потоке указанного ей метода.
Предложенная стратегия организации вычислений позволяет отделить блок сервисных функций распараллеливания от начальной функциональности класса. Взаимодействие между ними сводится к вызову единственной функции, позволяющей запустить любой метод в несколько потоков. При этом автоматически определяется количество процессоров и ядер системы.
Распараллеливание обработки массива данных из 80 млн. чисел с плавающей запятой с помощью разработанной стратегии позволило организовать от одного до 1200 потоков. Накладные расходы на создание и удаление потоков оказались незначительными и оставались в пределах секунды.
В ближайшее время прогнозируют стремительный рост количества ядер процессоров. На 100-ядерном процессоре аналогичный цикл с одинаковым числом итераций будет выполнен за один шаг.
6. Тестирование и верификация программного продукта
В процессе тестирования разработанного приложения осуществляется сравнение результатов с данными, получаемыми с помощью программного комплекса Espresso - широко используемым и общепризнанным лидером в области минимизации булевых функций.
Т естирование проводится в три этапа:
- сравнение временных характеристик систем Essential Variables и Espresso;
53
- сравнение качества минимизации функций;
- анализ масштабируемости приложения.
На первом и втором этапах используются сгенерированные случайным образом наборы (табл. 3).
Таблица 3. Характеристики тестовых наборов
1 2 3 4 5 6 7 8
Переменные 10 15 30 50 75 100 150 200
Наборы 50 100 300 500 1000 1500 2000 3000
Для тестирования используется вычислительная система следующей конфигурации:
процессор: Intel Core2 Duo 4300 1,8 GHz;
системная плата: Asus P5LD2 SE/C;
память: DDR2 Corsair 1 Gb 667 MHz.
Рис. 2. Временные характеристики методов
Как можно увидеть из графика (рис. 2), разработанное приложение значительно выигрывает у Espresso в скорости минимизации функций большого числа переменных. Уже для седьмого тестового набора время минимизации у Espresso было бы свыше двух часов, поэтому тестирование по наборам 7 и 8 не проводилось. Превосходство по быстродействию было достигнуто за счет следующих основных факторов:
- компактность и эффективность внутренних структур данных и алгоритмов их обработки, большинство из которых характеризуется линейной вычислительной сложностью;
- новая стратегия вычислений;
- применение современных технологий и инструментальных средств разработки (Microsoft Visual Studio 2005, Intel Compiler 9.1, VTune Performance Analyzer 9.0).
Анализ качества полученной функции показывает, что программное средство Essential Variables примерно в два раза проигрывает по сравнению с результатами системы Espresso (рис. 3). Улучшение этого показателя возможно за счет использования более эффективного метода выбора покрытия импликант-ной матрицы [5,6].
Тестовый набор
Рис. 3. Качество минимизации
Распараллеливание процесса заполнения ДНФ позволяет ускорить работу программного средства пропорционально количеству ядер системы. На рис. 4 показана зависимость между временем минимизации и количеством задействованных ядер системы. Масштабируемость проверяется на восьмом тестовом наборе. При этом используется высокопроизводительная серверная платформа на базе двух четырехядерных процессоров Intel Quad-Core Xeon E5335 2 GHz, 8 Mb Cache.
Рис. 4. Масштабируемость приложения в зависимости от количества ядер/процессоров системы
Результаты тестирования показали, что метод, реализованный в программном средстве Essential Variables, позволяет обеспечить высокий коэффициент масштабируемости и может применяться не только на современных системах архитектуры Intel, но и на мультиядерных системах следующего поколения, так как динамически адаптируется к количеству задействованных ядер процессора.
7. Выводы
Научная новизна исследования заключается в разработке квазиоптимального метода быстрой минимизации частично определенных булевых функций от большого числа переменных и его программной реализации. Метод ориентирован на современную мультиядерную архитектуру Intel, обладает высокой степенью масштабируемости и позволяет значительно сократить функцию от такого числа переменных, на котором другие методы уже не в состоянии выполнить минимизацию.
54
РИ, 2007, № 3
Практическая значимость: в сравнении с лидирующей в данной сфере системой Espresso, широко используемой в настоящее время, получено значительное превосходство предложенного метода по скорости (в 55 раз на тестовом наборе с наибольшим числом переменных) без значительного ухудшения качества результирующей функции. Это позволяет значительно сократить временные и, как следствие, финансовые затраты на проектирование цифровой техники.
Дальнейшие исследования будут направлены на разработку эффективного параллельного метода нахождения минимального покрытия импликантной матрицы для улучшения качества выходной ДНФ функции, а также на исследование и программную реализацию метода минимизации с помощью граф-схем [10].
Литература: 1. Закревский А.Д., ПоттосинЮ.В., Череми-синова Л.Д. Основы логического проектирования. Оптимизация в булевом пространстве. Кн. 2. Мн.: ОИПИ НАН Беларуси. 2004. 250 с. 2. R. K. Brayton, G.D.Hatchel, C. T. McMullen, A. Sangiovanni Vincentelli, ESPRESSO-II: a New Logic Minimizer for PLA //IEEE Pros. Custom Integrated Circuits Conf. 1984. 650 p. 3. Rudel R. and Sangiovanni Vincentelli A. Multiple-Valued Minimization for PLA Optimization // IEEE Trans. Computer-Aided Des. Vol. CAD-
6. No 5. Sept. 1987. P. 727-751. 4. Logic Synthesis and Optimization Benchmarks. User Guide: Version 3.0. Saeyang Yang. Technical Report. Microelectronics of North Carolina. 1991.570 p. 5. S.Minato. Fast Factorization Method for Implicit Cube Cover Representation // IEEE Trans. CAD. Vol. 15. No 4. April 1996. 560 p. 6. T. Sasao, ed., Representation of Discrete Functions. Kluwer Academic Publishers. May 1996. 685 p. 7.
Stroustrup B. The C++ programming Language. 3rd edition. Addison-Wesley. 2006. 990 p. 8. Shameem Akhter, Jason Roberts. Multi-Core Programming. Intel Press. 2006. 270 p. 9. Richard Gerber, Aart J.C. Bik, Kevin B. Smith, Xinmin Tian. The Software Optimization Cookbook. Intel Press. 2006. 430 p. 10. Кривуля Г.Ф. Минимизация не полностью определенных функций определенных переключательных функций с помощью граф-схем // АСУ и приборы автоматики. Х.: Вища школа. 1981. Вып.57. С.87-96.
Поступила в редколлегию 21.07.2007
Рецензент: д-р техн.наук, проф. Кривуля Г.Ф.
Михтонюк Сергей Владимирович, студент второго курса факультета компьютерной инженерии и управления ХНУРЭ. Научные интересы: параллельное, системное программирование, проектирование и оптимизация ПО. Адрес: Украина, 61166, Харьков, пр. Ленина, 14.
Давыдов Максим Дмитриевич, студент второго курса факультета компьютерной инженерии и управления ХНУРЭ. Научные интересы: параллельное программирование, оптимизация прикладных программ. Адрес: Украина, 61166, Харьков, пр. Ленина, 14.
Кузнецов Евгений Сергеевич, студент второго курса факультета компьютерной инженерии и управления ХНУРЭ. Научные интересы: параллельное программирование, оптимизация прикладных программ, Адрес: Украина, 61166, Харьков, пр. Ленина, 14.
Парфентий Александр Николаевич, аспирант кафедры АПВТ ХНУРЭ. Научные интересы: автоматизация моделирования, верификации и тестирования систем на кристаллах. Адрес: Украина, 61166, Харьков, пр. Ленина, 14, тел. 70-21-326.
УДК 519.713
КОДИРОВАНИЕ ДЕСТРУКТИВНЫХ ОБЪЕКТОВ НА ОСНОВЕ СИГНАТУРНОГО АНАЛИЗА
ГОРОБЕЦ А.А.,ЛОКТИН А.А., КРАСНОЯРУЖСКАЯ К.Ш._____________________
Исследуется проблема адекватности реакции антивирусных компаний возрастающим вирусным угрозам, выделяется задача ускорения процесса добавления вирусных сигнатур в базу данных и предлагается модель такого алгоритма.
1. Введение
Задача исследования - выявление узких мест в технологиях антивирусной индустрии для предложения метода ускорения реакции на возрастающие вирусные угрозы.
Цель исследования - разработка метода принятия быстрого решения о включении вирусной сигнатуры в базу вирусных сигнатур за счет существенного уменьшения области анализа базы данных путем сжатия вирусной сигнатуры посредством LFSR и использования полученного в остатке кода в качестве табличного индекса, который определяет область быстрого перебора несжатых вирусных сигнатур. Любое со-
кращение во времени этого процесса может оказаться решающим фактором в борьбе с распространением вирусной эпидемии.
Актуальность исследования вызвана тем, что задержка своевременного внесения вирусной сигнатуры в антивирусные базы влечет за собой серьезные последствия. Например, вирусный червь «Slammer» в конце января 2003 года смог «обездвижить» около 25% интернета: через 5 минут после начала эпидемии были заражены компьютеры в Южной Корее, Японии, США, Западной Европе, а через 15-20 минут было заражено более 100 тысяч компьютеров [1]. Современный высокотехнологичный мир попадает все в большую зависимость от бесперебойного и достоверного функционирования информационных систем - будь то отдельные мелкие предприятия, глобальные корпорации или государственные структуры. Все больше эти системы начинают функционировать с исполь-зованим сети Internet не только для поиска различной информации, но для оказания услуг, а также для передачи критически важной информации, как то -проведение платежей через различные платежные системы, обмен критически важной внутрикорпоративной информацией через защищенные VPN -туннели между территориально разнесенными подразделениями корпорации и др. Уже не вызывает сомнений то, что атаки, совершаемые отдельными криминаль-
РИ, 2007, № 3
55