Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #8976 (closed enhancement: fixed)

Opened 1 year ago

Last modified 1 year ago

[PATCH] optimization to AR::Base.exists?

Reported by: hasmanyjosh Assigned to: core
Priority: normal Milestone: 1.x
Component: ActiveRecord Version: edge
Severity: normal Keywords: tiny verified
Cc:

Description

Tiny patch to speed up AR::Base.exists?. It restricts the result to return only the id column instead of all columns. My naive benchmarks show a 3-5% speedup. Not a hugely imporant place to optimize, but it's low-hanging fruit.

All AR tests pass, but I've only run tests on MySQL. Anyone who can try it out on other dbs, please do so and leave a comment here as to the results.

Attachments

simple_optimized_exists.diff (0.7 kB) - added by hasmanyjosh on 07/13/07 17:40:19.
AR::Base exists? speedup
perform_exist.rb (2.1 kB) - added by lifofifo on 07/15/07 15:55:43.
Benchmarking for various implementations
exists_should_use_count.patch (1.3 kB) - added by veader on 07/19/07 03:05:20.
use count instead of find for further performance gains

Change History

07/13/07 17:40:19 changed by hasmanyjosh

  • attachment simple_optimized_exists.diff added.

AR::Base exists? speedup

07/13/07 21:18:39 changed by josh

+1 Awesome

07/14/07 22:20:23 changed by tarmo

+1

07/15/07 15:55:05 changed by lifofifo

I modified Josh's implementation a little and benchmarked it. The performance is slightly better. But ignorable I feel. Still uploading the results anyways so that others can test as well on different platform/db. My results are on Intel Based Mac/MySQL 5.0.x

Thanks.

+1

07/15/07 15:55:43 changed by lifofifo

  • attachment perform_exist.rb added.

Benchmarking for various implementations

(follow-up: ↓ 6 ) 07/15/07 16:07:18 changed by lifofifo

My version was faster because I removed the rescue block. So +1 for original patch.

07/15/07 16:46:29 changed by tarmo

All tests pass on postgresql and there is a tiny performance improvement: http://pastie.caboo.se/78987

(in reply to: ↑ 4 ; follow-up: ↓ 7 ) 07/15/07 17:48:37 changed by josh

Replying to lifofifo:

My version was faster because I removed the rescue block. So +1 for original patch.

In Lifo's Implementation the syntax is a bit awkward. However it is faster, and this is an optimization patch.

I could see someone submitting a refactor patch saying "Just use find(:first..) instead" because they have no idea why its that way. Maybe a nice comment that says "!find(:all, ..., :limit => 1).empty? is faster than find(:first)" will help clear things up.

(in reply to: ↑ 6 ; follow-up: ↓ 8 ) 07/15/07 17:53:57 changed by hasmanyjosh

Replying to josh:

In Lifo's Implementation the syntax is a bit awkward. However it is faster, and this is an optimization patch.

Well, Lifo's version misses the rescue clause, so it is not functionally equivalent. I'm not sure why exists? needs the rescue, though my guess is that it catches bogus conditions and someone thought that was necessary. Lifo himself said to use my version after I pointed that out to him.

This is piece of code is of such minor impact that it's not worth debating. It's just low-hanging fruit to do it my way, and we shouldn't waste time wondering if we can get another 1% optimization by getting rid of the rescue clause and not break anything.

(in reply to: ↑ 7 ; follow-up: ↓ 9 ) 07/15/07 17:59:55 changed by josh

Replying to hasmanyjosh:

I'm not sure why exists? needs the rescue, though my guess is that it catches bogus conditions and someone thought that was necessary.

Because find(id) raises a AR::NotFound (so it can show a 404). find(:all) and find(:first) just return false if it can't be found.

(in reply to: ↑ 8 ) 07/15/07 18:07:20 changed by hasmanyjosh

Replying to josh:

Because find(id) raises a AR::NotFound (so it can show a 404). find(:all) and find(:first) just return false if it can't be found.

Look at my actual patch. It uses find(:first) too. The only real difference between my patch and Lifo's is the rescue clause.

07/15/07 18:12:35 changed by josh

I guess you shouldn't need it.

>> Page.find(999)
ActiveRecord::RecordNotFound: Couldn't find Page with ID=999
>> Page.find(:first, :conditions => "id = 999")
=> nil
>> Page.find(:all, :conditions => "id = 999", :limit => 1)
=> []

07/15/07 18:25:30 changed by josh

Nevermind anything I've said... its a rescue for ActiveRecordError.

07/15/07 18:30:41 changed by tarmo

This is why the rescue is this:

------------------------------------------------------------------------
r983 | david | 2005-03-23 02:57:38 +0200 (K, 23 märts 2005) | 1 line

Fixed that AR exists?() would explode on postgresql if the passed id did not match the PK type #900 [Scott Barron]
------------------------------------------------------------------------

# svn diff -r 982:983 base.rb
Index: base.rb
===================================================================
--- base.rb	(revision 982)
+++ base.rb	(revision 983)
@@ -330,7 +330,7 @@
       # Example:
       #   Person.exists?(5)
       def exists?(id)
-        !find_first("#{primary_key} = #{sanitize(id)}").nil?
+        !find_first("#{primary_key} = #{sanitize(id)}").nil? rescue false
       end
 
       # This method is deprecated in favor of find with the :conditions option.

With postgresql 8.2.4 this does not seem to be a problem, not sure even if this is a problem with postgresql itself or the postgresql adapter, but to me this does not seem to be a good way to fix such an issue.

07/19/07 03:05:20 changed by veader

  • attachment exists_should_use_count.patch added.

use count instead of find for further performance gains

07/19/07 03:07:12 changed by veader

i've uploaded a patch that i created before i stumbled upon this ticket tonight. i ran it by josh as well. i believe using count may be better suited for performance gains here.

also added some simple unit tests for the exists method.

07/19/07 04:05:18 changed by hasmanyjosh

Looks like the count approach is actually slower. My benchmarks:

                     user     system      total        real
exists?         16.110000   1.510000  17.620000 ( 24.378860)  # original
exists_opt?     15.050000   1.500000  16.550000 ( 21.901476)  # simple optimization
exists_count?   17.930000   1.530000  19.460000 ( 24.787894)  # count optimization

07/19/07 04:40:08 changed by veader

agreed (after some benchmarking)

although, my tests did show that the count method was quicker than the original.

either way, +1 for josh's patch.

07/31/07 22:46:36 changed by lifofifo

  • keywords changed from tiny to tiny verified.

08/05/07 01:08:32 changed by nzkoz

  • status changed from new to closed.
  • resolution set to fixed.

fixed in [7274]