podigee/device_detector

Introduce a separate cache for the regexp definitions not to expire them with results

Closed this issue · 0 comments

While using the DeviceDetector gem I've noticed, that the same cache is being used to store the final detection results as well as to tackle the internal caching.

This approach causes a YAML reload of regexps when enough browsers are being recognized.

Here's an example of how to reproduce that:

require 'device_detector'

DeviceDetector.configure do |config|
  config.max_cache_keys = 10
end

module Bench
  def self.call
    t = Time.now.to_f
    r = yield
    p (Time.now.to_f-t)*1000
    r
  end
end

20.times do
  Bench.call { DeviceDetector.new(Time.now.to_f.to_s).full_version }
end

the result is:

16.92676544189453
0.14138221740722656
0.1392364501953125
0.1327991485595703
0.13113021850585938
28.29146385192871
0.7753372192382812
0.11897087097167969
0.11181831359863281
45.581817626953125
1.2042522430419922
1.0809898376464844
1.1005401611328125
17.076730728149414
0.12612342834472656
0.11444091796875
0.10657310485839844
27.06766128540039
0.13065338134765625
0.1087188720703125

So we see spikes in the time needed due to reloading of yaml definitions.

After applying the following patch:

module Patches
  module DeviceDetectorParser
    CACHE = ::DeviceDetector::MemoryCache.new({})

    private_constant :CACHE

    def regexes_for(file_paths)
      CACHE.get_or_set(filepaths) do
        super
      end
    end
  end
end

DeviceDetector::Parser.prepend(Patches::DeviceDetectorParser)

the results are always consistent as only the "public" results are being purged while keeping the regexp definitions intact in the cache.

require 'device_detector'

DeviceDetector.configure do |config|
  config.max_cache_keys = 10
end

module Patches
  module DeviceDetectorParser
    CACHE = ::DeviceDetector::MemoryCache.new({})

    private_constant :CACHE

    def regexes_for(file_paths)
      CACHE.get_or_set(filepaths) do
        super
      end
    end
  end
end

DeviceDetector::Parser.prepend(Patches::DeviceDetectorParser)

module Bench
  def self.call
    t = Time.now.to_f
    r = yield
    p (Time.now.to_f-t)*1000
    r
  end
end

20.times do
  Bench.call { DeviceDetector.new(Time.now.to_f.to_s).full_version }
end

Result:

19.721269607543945
1.1222362518310547
0.8854866027832031
0.1773834228515625
0.1361370086669922
0.1354217529296875
0.1327991485595703
0.13184547424316406
0.1277923583984375
0.13184547424316406
0.12993812561035156
0.1289844512939453
0.13017654418945312
0.13184547424316406
0.12826919555664062
0.13208389282226562
0.13065338134765625
0.12731552124023438
0.12969970703125
0.13637542724609375

if that approach is ok, I can make a PR.