Optimize globbed file lookups, fixes nasty performance regression

This commit is contained in:
Thomas Reynolds 2014-08-24 17:10:25 -07:00
parent 6ef96cc15a
commit 84acb50b02
14 changed files with 52 additions and 60 deletions

View file

@ -60,7 +60,8 @@ module Middleman::Cli
builder = Middleman::Builder.new(@app, builder = Middleman::Builder.new(@app,
glob: options['glob'], glob: options['glob'],
clean: options['clean']) clean: options['clean'],
parallel: options['parallel'])
builder.on_build_event(&method(:on_event)) builder.on_build_event(&method(:on_event))

View file

@ -1,18 +0,0 @@
Feature: Instance Vars
In order to share data with layouts and partials via instance variables
Scenario: Setting an instance var in a template should be visible in its layout
Given the Server is running at "more-instance-vars-app"
When I go to "/instance-var-set.html"
Then I should see "Var is 100"
Scenario: Setting an instance var in a template should be visible in a partial
Given the Server is running at "more-instance-vars-app"
When I go to "/instance-var-set.html"
Then I should see "My var is here!"
Scenario: Setting an instance var in one file should not be visible in another
Given the Server is running at "more-instance-vars-app"
When I go to "/instance-var-set.html"
When I go to "/no-instance-var.html"
Then I should see "No var..."

View file

@ -11,11 +11,6 @@ Feature: Provide Sane Defaults for Partial Behavior
When I go to "/sub/index.html" When I go to "/sub/index.html"
Then I should see "Header" Then I should see "Header"
And I should see "Footer" And I should see "Footer"
Scenario: Finds shared partials without _ prefix
Given the Server is running at "partials-app"
When I go to "/using_snippet.html"
Then I should see "Snippet"
Scenario: Prefers partials of the same engine type Scenario: Prefers partials of the same engine type
Given the Server is running at "partials-app" Given the Server is running at "partials-app"

View file

@ -1,5 +0,0 @@
<% if @my_var %>
My var is here!
<% else %>
No var...
<% end %>

View file

@ -1,2 +0,0 @@
<% @my_var = 100 %>
<%= partial 'vartial' %>

View file

@ -1,3 +0,0 @@
Var is <%= @my_var %>
<%= yield %>

View file

@ -1 +0,0 @@
<%= partial 'vartial' %>

View file

@ -50,7 +50,9 @@ module Middleman
queue_current_paths if @cleaning queue_current_paths if @cleaning
prerender_css prerender_css
output_files output_files
clean if @cleaning clean if @cleaning
::Middleman::Profiling.report('build') ::Middleman::Profiling.report('build')

View file

@ -82,8 +82,7 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
resources.each do |resource| resources.each do |resource|
# If it uses file extension localization # If it uses file extension localization
if parse_locale_extension(resource.path) if result = parse_locale_extension(resource.path)
result = parse_locale_extension(resource.path)
ext_lang, path, page_id = result ext_lang, path, page_id = result
new_resources << build_resource(path, resource.path, page_id, ext_lang) new_resources << build_resource(path, resource.path, page_id, ext_lang)
# If it's a "localizable template" # If it's a "localizable template"

View file

@ -156,7 +156,7 @@ module Middleman
# @return [Array<Middleman::SourceFile>] # @return [Array<Middleman::SourceFile>]
Contract None => ArrayOf[SourceFile] Contract None => ArrayOf[SourceFile]
def files def files
watchers.map(&:files).flatten.uniq { |f| f[:relative_path] } watchers.flat_map(&:files).uniq { |f| f[:relative_path] }
end end
# Find a file given a type and path. # Find a file given a type and path.
@ -168,9 +168,10 @@ module Middleman
Contract Symbol, String, Maybe[Bool] => Maybe[SourceFile] Contract Symbol, String, Maybe[Bool] => Maybe[SourceFile]
def find(type, path, glob=false) def find(type, path, glob=false)
watchers watchers
.lazy
.select { |d| d.type == type } .select { |d| d.type == type }
.map { |d| d.find(path, glob) } .map { |d| d.find(path, glob) }
.compact .reject { |d| d.nil? }
.first .first
end end
@ -182,6 +183,7 @@ module Middleman
Contract Symbol, String => Bool Contract Symbol, String => Bool
def exists?(type, path) def exists?(type, path)
watchers watchers
.lazy
.select { |d| d.type == type } .select { |d| d.type == type }
.any? { |d| d.exists?(path) } .any? { |d| d.exists?(path) }
end end
@ -194,6 +196,7 @@ module Middleman
Contract Symbol, String => Maybe[HANDLER] Contract Symbol, String => Maybe[HANDLER]
def watcher_for_path(type, path) def watcher_for_path(type, path)
watchers watchers
.lazy
.select { |d| d.type == type } .select { |d| d.type == type }
.find { |d| d.exists?(path) } .find { |d| d.exists?(path) }
end end

View file

@ -43,6 +43,7 @@ module Middleman
@directory = Pathname(directory) @directory = Pathname(directory)
@files = {} @files = {}
@extensionless_files = {}
@validator = options.fetch(:validator, proc { true }) @validator = options.fetch(:validator, proc { true })
@ignored = options.fetch(:ignored, proc { false }) @ignored = options.fetch(:ignored, proc { false })
@ -105,8 +106,7 @@ module Middleman
p = @directory + p if p.relative? p = @directory + p if p.relative?
if glob if glob
found = @files.find { |_, v| v[:relative_path].fnmatch(path) } @extensionless_files[p]
found ? found.last : nil
else else
@files[p] @files[p]
end end
@ -215,7 +215,7 @@ module Middleman
.select(&method(:valid?)) .select(&method(:valid?))
valid_updates.each do |f| valid_updates.each do |f|
@files[f[:full_path]] = f add_file_to_cache(f)
logger.debug "== Change (#{f[:type]}): #{f[:relative_path]}" logger.debug "== Change (#{f[:type]}): #{f[:relative_path]}"
end end
@ -225,13 +225,23 @@ module Middleman
.select(&method(:valid?)) .select(&method(:valid?))
valid_removes.each do |f| valid_removes.each do |f|
@files.delete(f[:full_path]) remove_file_from_cache(f)
logger.debug "== Deletion (#{f[:type]}): #{f[:relative_path]}" logger.debug "== Deletion (#{f[:type]}): #{f[:relative_path]}"
end end
run_callbacks(@on_change_callbacks, valid_updates, valid_removes) unless valid_updates.empty? && valid_removes.empty? run_callbacks(@on_change_callbacks, valid_updates, valid_removes) unless valid_updates.empty? && valid_removes.empty?
end end
def add_file_to_cache(f)
@files[f[:full_path]] = f
@extensionless_files[f[:full_path].sub_ext('.*')] = f
end
def remove_file_from_cache(f)
@files.delete(f[:full_path])
@extensionless_files.delete(f[:full_path].sub_ext('.*'))
end
# Check if this watcher should care about a file. # Check if this watcher should care about a file.
# #
# @param [Middleman::SourceFile] file The file. # @param [Middleman::SourceFile] file The file.

View file

@ -102,6 +102,7 @@ module Middleman
partial_file = locate_partial(name) partial_file = locate_partial(name)
return "" unless partial_file
raise ::Middleman::TemplateRenderer::TemplateNotFound, "Could not locate partial: #{name}" unless partial_file raise ::Middleman::TemplateRenderer::TemplateNotFound, "Could not locate partial: #{name}" unless partial_file
source_path = sitemap.file_to_path(partial_file) source_path = sitemap.file_to_path(partial_file)

View file

@ -9,11 +9,24 @@ module Middleman
extend Forwardable extend Forwardable
include Contracts include Contracts
def self.cache class Cache
@_cache ||= ::Tilt::Cache.new def initialize
@cache = {}
end
def fetch(*key)
@cache[key] = yield unless @cache.key?(key)
@cache[key]
end
def clear
@cache = {}
end
end end
def_delegator :"self.class", :cache def self.cache
@_cache ||= Cache.new
end
# Custom error class for handling # Custom error class for handling
class TemplateNotFound < RuntimeError; end class TemplateNotFound < RuntimeError; end
@ -163,21 +176,9 @@ module Middleman
# @return [String, Boolean] Either the path to the template, or false # @return [String, Boolean] Either the path to the template, or false
Contract IsA['Middleman::Application'], Or[Symbol, String], Maybe[Hash] => Maybe[IsA['Middleman::SourceFile']] Contract IsA['Middleman::Application'], Or[Symbol, String], Maybe[Hash] => Maybe[IsA['Middleman::SourceFile']]
def self.resolve_template(app, request_path, options={}) def self.resolve_template(app, request_path, options={})
# Find the path by searching or using the cache # Find the path by searching
relative_path = Util.strip_leading_slash(request_path.to_s) relative_path = Util.strip_leading_slash(request_path.to_s)
# Cache lookups in build mode only
if app.build?
cache.fetch(:resolve_template, relative_path, options) do
uncached_resolve_template(app, relative_path, options)
end
else
uncached_resolve_template(app, relative_path, options)
end
end
Contract IsA['Middleman::Application'], String, Hash => Maybe[IsA['Middleman::SourceFile']]
def self.uncached_resolve_template(app, relative_path, options)
# By default, any engine will do # By default, any engine will do
preferred_engines = [] preferred_engines = []
@ -200,7 +201,16 @@ module Middleman
path_with_ext = relative_path.dup path_with_ext = relative_path.dup
path_with_ext << ('.' + preferred_engine) unless preferred_engine.nil? path_with_ext << ('.' + preferred_engine) unless preferred_engine.nil?
file = app.files.find(:source, path_with_ext, preferred_engine == '*') globbing = preferred_engine == '*'
# Cache lookups in build mode only
file = if app.build?
cache.fetch(path_with_ext, preferred_engine) do
app.files.find(:source, path_with_ext, globbing)
end
else
app.files.find(:source, path_with_ext, globbing)
end
found_template = file if file && (preferred_engine.nil? || ::Tilt[file[:full_path]]) found_template = file if file && (preferred_engine.nil? || ::Tilt[file[:full_path]])
break if found_template break if found_template