This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

Cache results from FileScanner#scan.

Before this up to 2 calls to Dir.glob could be made every time FileScanner#scan
was called. For an average file referring to a set of constants this could
introduce significant overhead.

This commit changes the workings of FileScanner#scan so that it does two things
differently:

1. All possible files are globbed once and then stored.
2. The results of FileScanner#scan are cached per constant path.

In terms of numbers, before this commit the script `benchmark/bootup.rb` would
report an average exection time of around 1,347707 seconds. This commit reduces
that to around 0,903891 seconds, a difference of around 440 miliseconds.

Although the decrease might not seem like much, especially for Ruby scripts, it
means that it becomes more attractive to use ruby-lint in an editor without
having to worry about it locking up the entire editor. For example, when using
Syntastic for Vim in the above scenario this would mean that Vim, since it
doesn't run plugins in an asynchronous manner, would be locked up for almost
half a second *at least*.

Benchmarks were performed on a standard Thinkpad T520 with a 5400 RPM HDD
running Linux 3.11.6. Numbers may vary for those who use an SSD.

See #61 for more information.
  • Loading branch information...
YorickPeterse committed Nov 14, 2013
1 parent d1fbb6e commit 47ab299a0735e9dd03429d8c8954baff736cb84b
Showing with 44 additions and 11 deletions.
  1. +44 −11 lib/ruby-lint/file_scanner.rb
@@ -23,6 +23,13 @@ def initialize(directories = [Dir.pwd], ignore = [])

@directories = directories
@ignore = ignore || []

# Hash that will contain the matching file paths for a given constant.
@constant_paths_cache = {}

# Globbing all files at once and then comparing those results is faster
# than running a Dir.glob for every call to #scan.
@glob_cache = Dir.glob("#{directories.join(',')}/**/*.rb")
end

##
@@ -34,41 +41,67 @@ def initialize(directories = [Dir.pwd], ignore = [])
# @return [Array]
#
def scan(constant)
segment = constant_to_path(constant)
paths = Dir.glob(glob_pattern(segment))
unless constant_paths_cached?(constant)
build_constant_paths_cache(constant)
end

return @constant_paths_cache[constant]
end

private

##
# Searches all the files that could potentially define the given constant
# and caches them.
#
# @param [String] constant
#
def build_constant_paths_cache(constant)
paths = match_globbed_files(constant_to_path(constant))

# Lets see if we can find anything when using dashes for the directory
# names instead of underscores.
if paths.empty?
segment = constant_to_dashed_path(constant)
paths = Dir.glob(glob_pattern(segment))
paths = match_globbed_files(constant_to_dashed_path(constant))
end

paths.map! { |path| File.expand_path(path) }
paths.map! { |p| File.expand_path(p) }

ignore.each do |pattern|
paths.reject! do |path|
path.include?(pattern)
end
end

# Ensure that the order is from top-level -> deeply nested files instead
# of a random order.
# Ensure that the order is from top-level -> deeply nested files
# instead of a random order.
paths.sort! do |left, right|
left.length <=> right.length
end

return paths
@constant_paths_cache[constant] = paths
end

private
##
# @return [Array]
#
def match_globbed_files(segment)
return @glob_cache.select { |p| p.include?(segment) }
end

##
# @return [TrueClass|FalseClass]
#
def constant_paths_cached?(constant)
return @constant_paths_cache.key?(constant)
end

##
# @param [String] constant
# @return [String]
#
def constant_to_path(constant)
return constant.gsub('::', '/').snake_case
return constant.gsub('::', '/').snake_case + '.rb'
end

##
@@ -79,7 +112,7 @@ def constant_to_dashed_path(constant)
last = segments[-1]
path = segments[0..-2].join('/').snake_case.gsub('_', '-')

return "#{path}/#{last.snake_case}"
return "#{path}/#{last.snake_case}.rb"
end

##

0 comments on commit 47ab299

Please sign in to comment.