Skip to content

Commit

Permalink
Speed up digest with recursive hash
Browse files Browse the repository at this point in the history
Digest is the second most time spent while compiling assets on the first run for CodeTriage. The `if/elsif` means that for complex data structures many many comparisons are made. Instead we store the logic of digesting each class in a hash with the class name as a key. Complex data types digest themselves by recursively calling into the hash.

Is it faster?

On CodeTriage.com over 50 runs against this codebase and master it averaged 12.54s per run on this branch stdev 0.27seconds and against master it was 14.9s with a 2.045 stdev. So in the average case it makes asset generation 16% faster.
  • Loading branch information
schneems committed Jun 20, 2016
1 parent 88fca20 commit 3f0f651
Showing 1 changed file with 45 additions and 39 deletions.
84 changes: 45 additions & 39 deletions lib/sprockets/digest_utils.rb
Expand Up @@ -35,6 +35,50 @@ def detect_digest_class(bytes)
DIGEST_SIZES[bytes.bytesize]
end

ADD_VALUE_TO_DIGEST = {
String => ->(val, digest) { digest << val },
FalseClass => ->(val, digest) { digest << 'FalseClass'.freeze },
TrueClass => ->(val, digest) { digest << 'TrueClass'.freeze },
NilClass => ->(val, digest) { digest << 'NilClass'.freeze },

Symbol => ->(val, digest) {
digest << 'Symbol'.freeze
digest << val.to_s
},
Fixnum => ->(val, digest) {
digest << 'Fixnum'.freeze
digest << val.to_s
},
Bignum => ->(val, digest) {
digest << 'Bignum'.freeze
digest << val.to_s
},
Array => ->(val, digest) {
digest << 'Array'.freeze
val.each do |element|
ADD_VALUE_TO_DIGEST[element.class].call(element, digest)
end
},
Hash => ->(val, digest) {
digest << 'Hash'.freeze
val.sort.each do |array|
ADD_VALUE_TO_DIGEST[Array].call(array, digest)
end
},
Set => ->(val, digest) {
digest << 'Set'.freeze
ADD_VALUE_TO_DIGEST[Array].call(val.to_a, digest)
},
Encoding => ->(val, digest) {
digest << 'Encoding'.freeze
digest << val.name
},
}
ADD_VALUE_TO_DIGEST.default_proc = ->(_, val) {
raise TypeError, "couldn't digest #{ val }"
}
private_constant :ADD_VALUE_TO_DIGEST

# Internal: Generate a hexdigest for a nested JSON serializable object.
#
# This is used for generating cache keys, so its pretty important its
Expand All @@ -45,46 +89,8 @@ def detect_digest_class(bytes)
# Returns a String digest of the object.
def digest(obj)
digest = digest_class.new
queue = [obj]

while queue.length > 0
obj = queue.shift
klass = obj.class

if klass == String
digest << obj
elsif klass == Symbol
digest << 'Symbol'
digest << obj.to_s
elsif klass == Fixnum
digest << 'Fixnum'
digest << obj.to_s
elsif klass == Bignum
digest << 'Bignum'
digest << obj.to_s
elsif klass == TrueClass
digest << 'TrueClass'
elsif klass == FalseClass
digest << 'FalseClass'
elsif klass == NilClass
digest << 'NilClass'.freeze
elsif klass == Array
digest << 'Array'
queue.concat(obj)
elsif klass == Hash
digest << 'Hash'
queue.concat(obj.sort)
elsif klass == Set
digest << 'Set'
queue.concat(obj.to_a)
elsif klass == Encoding
digest << 'Encoding'
digest << obj.name
else
raise TypeError, "couldn't digest #{klass}"
end
end

ADD_VALUE_TO_DIGEST[obj.class].call(obj, digest)
digest.digest
end

Expand Down

0 comments on commit 3f0f651

Please sign in to comment.