podigee/device_detector

Benchmarks need a little clarification and maybe a feature request

MadBomber opened this issue · 7 comments

Please correct me if I'm wrong... The actual parsing of the UA string does not take place until a specific component is requested. At that point only that specific component is extracted from the UA string. So other user agent parsers which extract all components at once will show up slower in your benchmarks.

I can see where your JIT parser approach comes in handy when only one or two components are used within an application. it would be interesting to see how your approach actually compares to a full parse of all components. For example Useragent now supports a #to_h method which provides a nicely structured representation of all of the components of the UA string.

If DeviceDetector had a #to_h feature that did the same thing, how would your regex approach compare benchmark-wise against UserAgent ?

I can see how a #to_h feature may be outside the scope for which DeviceDetector is best suited.

Dewayne
o-*

Yes, there are three main components, the browser / client (software), the operating system and the device (hardware).

The thing is, UserAgent doesn't do device detection at all, so having a benchmark that compares parsing the client name, os and device (DeviceDetector) with only name & os (UserAgent) wouldn't be fair at all.

The method #to_h that you propose is useful, but it can be achieved by the end-user by casting the results into a hash, so I don't know if there is a benefit of adding it to the gem.

Hi, I'm maintaining UserAgent nowadays and I'd like to be able to run your benchmark to understand where it's slow. Is user_agent_strings.txt available anywhere?

Also, where do you believe most of your performance comes from? I would have thought the caching could be quite significant but I suppose that depends on the content of user_agent_strings.txt.

Hello @gshutler!

Most of the performance comes from caching and optimizing the regex parsing and matching. It's no voodoo magic and actually we didn't want to make this a contest, we just wanted to ensure that future PRs do not compromise speed of detection. Our priority is to provide the most accurate detection while not giving up on a decent speed.

Unfortunately, the user_agent_strings.txt is not available, but it has ~200.000 raw user agent strings in it, ~5.000 being unique. This is a pretty common scenario, as the most popular platforms, browsers and devices are used more often than other, more exotic ones. Here, the caching helps a lot.

If user_agent_strings.txt isn't able to be made available could you run this for me to see how much it helps?

require 'device_detector'
require 'browser'
require 'user_agent'
require 'lru_redux'
require 'benchmark'

user_agent_strings = File.read('./tmp/user_agent_strings.txt').split("\n")

class CachedUserAgent
  DEFAULT_MAX_ITEMS = 5000

  def self.max_items
    @max_items ||= DEFAULT_MAX_ITEMS
  end

  def self.max_items=(value)
    @max_items = value
  end

  def self.cache
    @good_cache ||= LruRedux::ThreadSafeCache.new(max_items)
  end

  def self.parse(useragent)
    cache.getset(useragent) { UserAgent.parse(useragent) }
  end
end

## Benchmarks

Benchmark.bm(15) do |x|
  x.report('device_detector') {
    user_agent_strings.each { |uas| DeviceDetector.new(uas).name }
  }
  x.report('browser') {
    user_agent_strings.each { |uas| Browser.new(ua: uas).name }
  }
  x.report('useragent') {
    user_agent_strings.each { |uas| UserAgent.parse(uas).browser }
  }
  x.report('cached useragent') {
    user_agent_strings.each { |uas| CachedUserAgent.parse(uas).browser }
  }
end

I'd like to get a feel for how effective the cache is. Have you also done benchmarking with different sizes of cache (100, 250, 500, 1000, 2000 for example)? With your default cache size also roughly matching your unique user agent strings it would be interesting to see where the optimum memory/speed trade off lay.

@gshutler I got some good news for you ;)

Using the cached variant of User Agent it actually wins the race:

Cache size 5000
                      user     system      total        real
device_detector   1.170000   0.010000   1.180000 (  1.179326)
browser           2.010000   0.010000   2.020000 (  2.025717)
useragent         4.600000   0.010000   4.610000 (  4.605785)
cached useragent  0.560000   0.010000   0.570000 (  0.571583)

Also, I have detected that the cache size beyond 1.000 has a minimal impact on detection performance:

Cache size 1000
                      user     system      total        real
device_detector   1.160000   0.020000   1.180000 (  1.179425)
browser           2.030000   0.010000   2.040000 (  2.036658)
useragent         4.640000   0.010000   4.650000 (  4.660174)
cached useragent  0.610000   0.010000   0.620000 (  0.607808)

At Podigee we have noticed that caching user agent strings is beneficial to us. Obviously, each use case is different, but the good thing about caching is that it has a negligible impact on performance even if each user agent string is different (extremely uncommon).

Next thing that would be nice to measure is how good / precise UA detection is. Our code isn't going to get much faster, as the regex parsing and matching through hundreds of different options is quite expensive.

If it is OK with you, I'd like to close this issue.

Thank you. Good discussion. Lets close it.

Thank you too ;)