From 4c4b75ff77c7d26ff16e4427f9d4071c7c0020c2 Mon Sep 17 00:00:00 2001 From: Ali Ebrahim Date: Wed, 21 Sep 2022 00:36:30 -0500 Subject: [PATCH] Implement a threadpool for faster builds. Fixes #225 --- jekyll_picture_tag.gemspec | 2 ++ lib/jekyll_picture_tag/srcsets/basic.rb | 37 +++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/jekyll_picture_tag.gemspec b/jekyll_picture_tag.gemspec index d2ab9931..1be63fb6 100644 --- a/jekyll_picture_tag.gemspec +++ b/jekyll_picture_tag.gemspec @@ -29,6 +29,8 @@ Gem::Specification.new do |spec| # addressable is used to url-encode image filenames. spec.add_runtime_dependency 'addressable', '~> 2.6' + # Needed for parallel excution of builds. + spec.add_runtime_dependency 'concurrent-ruby', '~> 1.1' # Jekyll versions older than 4.0 are not supported. spec.add_runtime_dependency 'jekyll', '~> 4.0' # MIME types are needed for tags' type= attributes. diff --git a/lib/jekyll_picture_tag/srcsets/basic.rb b/lib/jekyll_picture_tag/srcsets/basic.rb index 446ba57b..7c05ae9f 100644 --- a/lib/jekyll_picture_tag/srcsets/basic.rb +++ b/lib/jekyll_picture_tag/srcsets/basic.rb @@ -1,4 +1,7 @@ require 'mime-types' +require 'set' +require 'concurrent-ruby' + module PictureTag # Handles srcset generation, which also handles file generation. module Srcsets @@ -62,16 +65,30 @@ def height_attribute private + # Defining a threadpool takes 5 lines, but isn't really that complex + # of an operation, so we disable the rubocop method length warning. + # rubocop:disable Metrics/MethodLength def build_files # By 'files', we mean the GeneratedImage class. return target_files if target_files.all?(&:exists?) # This triggers GeneratedImage to actually build an image file. - files = checked_targets - files.each(&:generate) + files = unique_checked_targets + pool = Concurrent::ThreadPoolExecutor.new( + min_threads: 1, max_threads: Concurrent.processor_count, + max_queue: 2 * Concurrent.processor_count + ) + files.each do |file| + pool.post do + file.generate + end + end + pool.shutdown + pool.wait_for_termination files end + # rubocop:enable Metrics/MethodLength def checked_targets if target_files.any? { |f| f.width > source_width } @@ -85,6 +102,22 @@ def checked_targets files || target_files end + def unique_checked_targets + # Because we're processing in parallel, we can't use existince + # in the filestystem to prevent duplicate handling of files without + # a race condition, so we manually track generated files here so we + # don't attempt to generate the same image twice. + seen_files = Set[] + unique_targets = [] + checked_targets.each do |target| + unless seen_files.include?(target.name) + seen_files.add(target.name) + unique_targets.push(target) + end + end + unique_targets + end + def source_width source_image.width end