Шрифт:
Интервал:
Закладка:
def process_multiple(filter : String, input_files :
Array(String), error : IO) : Nil
input_files.each do |file|
File.open(file, "r") do |input_file|
File.open("#{input_file.path}.transformed", "w") do
|output_file|
self.process [filter], input_file, output_file, error
end
end
end
end
Этот метод сводится к следующим шагам:
1. Определите новый метод, предназначенный для обработки нескольких входных файлов, который принимает фильтр и массив файлов для обработки.
2. Переберите каждый входной файл, используя метод File.open, чтобы открыть файл для чтения.
3. Снова используйте File.open, чтобы открыть выходной файл для записи, используя путь к входному файлу с добавлением .transformed в качестве имени выходного файла,
4. Вызовите метод одиночного ввода, передав наш фильтр в качестве единственного аргумента и используя открытые файлы в качестве входных и выходных операций IO.
Прежде чем мы сможем это протестировать, нам нужно сделать так, чтобы передача опции --multi заставляла CLI вызывать этот метод. Давайте сделаем это сейчас. Откройте src/transform_cli.cr и обновите его, чтобы он выглядел следующим образом:
require "./transform"
require "option_parser"
processor = Transform::Processor.new
multi_file_mode = false
OptionParser.parse do |parser|
parser.banner = "Usage: transform <filter> [options]
[arguments] [filename …]"
parser.on("-m", "--multi", "Enables multiple file input mode") { multi_file_mode = true }
parser.on("-h", "--help", "Show this help") do
puts parser
exit
end
end
begin
if multi_file_mode
processor.process_multiple ARGV.shift, ARGV, STDERR
else
processor.process ARGV, STDIN, STDOUT, STDERR
end
rescue ex : RuntimeError
exit 1
end
И снова на помощь приходит стандартная библиотека Crystal в виде типа OptionParser. Этот тип позволяет вам настроить логику, которая должна выполняться, когда эти параметры передаются через ARGV. В нашем случае мы можем использовать это для определения более удобного интерфейса, который также будет поддерживать параметры -h или --help. Кроме того, он позволяет вам реагировать на флаг --multi без необходимости вручную анализировать ARGV. Код довольно прост. Если флаг передан, мы устанавливаем для переменной multi_file_mode значение true, которое используется для определения того, какой метод процессора вызывать.
Чтобы проверить это, я создал несколько простых файлов YAML в корневом каталоге проекта. Не имеет большого значения, что они собой представляют, важно лишь то, что они действительны в формате YAML. Затем я собрал наш двоичный файл и запустил его с помощью ./bin/transform --multi. file1.yml file2.yml file3.yml, утверждая, что три выходных файла были созданы должным образом. У меня это заняло ~0,1 секунды. Давайте посмотрим, сможем ли мы улучшить это, реализовав параллельную версию метода process_multiple.
Вспоминая то, что мы узнали в последних двух разделах, чтобы сделать этот метод параллельным, нам понадобится инициировать открытие файла и обрабатывать логику внутри волокна. Затем нам понадобится канал, чтобы мы могли отслеживать завершенные файлы. В конечном итоге метод должен выглядеть так:
def process_multiple(filter : String, input_files :
Array(String), error : IO) : Nil
channel = Channel(Bool).new
input_files.each do |file|
spawn do
File.open(file, "r") do |input_file|
File.open("#{input_file.path}.transformed", "w")
do |output_file|
self.process [filter], input_file, output_file, error
end
end
ensure
channel.send true
end
end
input_files.size.times do
channel.receive
end
end
По сути, это то же самое, только с введением волокон для параллельности. Назначение канала — гарантировать, что основное волокно не выйдет из строя до завершения обработки всех файлов. Это достигается путем отправки значения true в канал после обработки файла и получения этого значения ожидаемое количество раз. Команда send находится внутри блока ensure для обработки сценария в случае сбоя процесса. Эта реализация требует немного большей доработки и будет рассмотрена в следующей главе. Я провел тот же тест, что и раньше, с параллельным кодом и получил значение от 0,03 до 0,06 секунды.
Я бы в любой день взял прирост производительности в 2-3 раза.
Резюме
И вот оно: одновременная обработка нескольких входных файлов! Параллельное программирование может быть ценным инструментом для создания высокопроизводительных приложений, позволяя разбивать рабочие нагрузки, связанные с IO, так, чтобы некоторая часть работы выполнялась постоянно. Кроме того, его можно использовать для уменьшения объема памяти приложения за счет одновременной обработки входных данных по мере их поступления, без необходимости ждать и загружать все данные в память.
На данный момент наш CLI почти готов! Теперь он может эффективно обрабатывать как одиночные, так и множественные входные файлы. Он может передавать данные в потоковом режиме, чтобы уменьшить использование памяти, и настроен для простой поддержки использования библиотек. Далее мы собираемся сделать что-то немного другое: мы собираемся поддерживать отправку уведомлений на рабочем столе о различных событиях в нашем CLI. Для этого в следующей главе мы узнаем о способности Crystal связываться с библиотеками C.
7. Совместимость c C
В этой главе основное внимание будет уделено одной из наиболее продвинутых функций Crystal: возможности взаимодействия с существующими библиотеками C путем написания привязок C. Эта функция Crystal позволяет повторно использовать высокооптимизированный и/или надежный код внутри Crystal, не написав ни строчки C и не беря на себя нетривиальную задачу по переносу всего этого в Crystal. Мы рассмотрим следующие темы:
• Знакомство с привязками C.
• Привязка libnotify
• Интеграция привязок
libnotify позволяет отправлять уведомления на рабочий стол в качестве средства предоставления пользователю ненавязчивой информации при возникновении событий. Мы собираемся использовать эту библиотеку для отправки собственных уведомлений.
К концу этой главы вы сможете писать привязки C для существующих библиотек и понимать, как лучше всего скрыть детали реализации привязок от конечного пользователя. Привязки C позволяют коду Crystal использовать высокооптимизированный код C или просто разрешать повторное использование кода без необходимости предварительного переноса всей библиотеки в Crystal.
Технические требования
Требования к этой главе следующие:
• Рабочая установка Кристалла.
• Рабочая установка jq.
• Рабочая установка libnotify.
• Рабочий компилятор C, например GCC.
Инструкции по настройке Crystal можно найти в Главе 1 «Введение в