Re: OOP slow -- am I an idiot?

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



For your rudimentary example of object-relational mapping below, yes, 
performance is going to be atrocious.  That's because you're not taking any 
advantage of the features that using OOP gives you.  One could write a 
dissertation on this problem, but I will just give you some general 
guidelines.  (All code examples below assume PHP 5.)

1) OOP has overhead.  In most situations, if you can write something in a 
fully OOP fashion or a direct procedural fashion, the direct procedural will 
perform faster.  How much faster depends on the situation and the experience 
level of the programmer.  What OOP can get you is flexibility.  There are 
cases where OOP code is faster than the corresponding procedural, but none of 
them involve SQL. :-)

2) Burn some memory on private variables.  That's what they're there for.  Eg:

public class Foo {
  private $id=0;
  private $bar=0;
  private $baz='';

  function __construct($id) {
    //  Do your type checking and escaping here
    $result = mysql_query("SELECT * FROM Foo WHERE id={$id}");	
    $record = mysql_fetch_object($result);
    $this->id = record->id;
    $this->bar = record->bar;
    $this->baz = record->baz;
  }

  function getBaz() {
    return $this->baz;
  }
}

You'll want to be a lot smarter than the code above to avoid drudge work in 
the code, but you get the idea.  Front-load as much of the object's 
information has you can in the constructor, within reason.  A good rule of 
thumb is that anything you can get in the initial query (like above) you 
should and then cache.  That saves you putting wasteful SQL queries in your 
getters.

3) Data you have to load later from another table, delay until you need to get 
it but then cache that as well.

function getRevenue ($id,$department,$month,$year) {
  if (!$this->revenue) {
        $result = mysql_query ("SELECT revenue FROM customer_revenue WHERE 
customer_id = '$id' AND
department = '$department' AND month = '$month' AND year = '$year'");
     $this->revenue = mysql_result ($result, 0);
  }
  return $this->revenue;
}

Again, pull extra data if you can.  A somewhat larger result set is (usually) 
better than multiple queries.  How much you want to front-load in your object 
and how much you want to defer to later depends on your specific problem and 
how frequently the data will be used.  

4) You can optimize your SQL structure for easier object usage.  Often that 
just means proper normalization, sometimes you'll want to denormalize in 
specific places to improve performance.  Again, depends on your situation.

5) If you need to grab 100 objects at once, but just need basic data out of 
them, use a factory.  Vis, 

$myobjects = Foo::getObjects(array(1, 2, 3));

class Foo {

  static function getObjects($ids) {
    $return = array();
    $result = mysql_query("SELECT * FROM Foo WHERE id IN (" . implode(',', 
$ids) . ")");
    while ($record = mysql_fetch_object($result)) {
      $foo = new Foo();
      $foo->setProperties($record);
      $return[] = clone($foo);
   }
   return $return;
  }

  function setProperties($properties) {
    // Left as an exercise to the reader
  }
}

6) If you need to do a complex query with a couple of joins and such, then 
don't waste your time or the computer's trying to shoe-horn it into SQL.  SQL 
is not inherently OO to start with!  Just write your query and loop it and be 
happy.  OOP is not the answer to all problems.  Sometimes it does just make 
matters worse, no matter what Sun tries to tell you. :-)

>  I want my data to _only_ be accessed from the black box called an OOP
>  class. 

That will work and is achievable in about 30% of all situations.  For the 
other 70%, you will have to just hunker down and *gasp* write SQL specific to 
the task at hand at least some of the time.  How much of the time once again 
depends on your situation and the problem you're trying to solve.

On Tuesday 10 October 2006 18:14, Chris de Vidal wrote:
> I think perhaps I'm using classes and OOP incorrectly.  The last time I
> used them, they were slow.
>
> I want to create a "customer" class which fetches its attributes from a
> MySQL database.  Something like this pseudocode:
>
> class customer
>     {
>     ...
>     getName ($id)
>         {
>         $result = mysql_query ("SELECT name FROM customers WHERE id =
> '$id'"); return mysql_result ($result, 0);
>         }
>     getRevenue ($id,$department,$month,$year)
>         {
>         $result = mysql_query ("SELECT revenue FROM customer_revenue WHERE
> customer_id = '$id' AND department = '$department' AND month = '$month' AND
> year = '$year'"); return mysql_result ($result, 0);
>         }
>     ...
>     }
>
> (Again, that's just psedocode.  Haven't written anything yet.)
>
>
> That works great for just one revenue result, but what if I want to return
> several results?  What if I want to build a report with hundreds of
> customers' revenues for a month?  For several months? For an entire year? 
> Doesn't it slow wayyyy down?  It seemed to in the past.  The method above
> doesn't seem to scale well.
>
> I did something like the above a few years ago.  It was slow, and I think
> it's because it was doing this for each single row:
> * Instantiate the class
> * Perform a query for the name
> * Perform a query for the first department
> * Perform a query for the next department
> * Perform a query for the next department
> * Perform a query for the next department
> * Perform a query for the next department
> * Perform a query for the next department
> * Perform a query for the next department
> ...
> * Destroy the instantiation
> * Start over
>
> I love using object-oriented programming, but it seems to me that's ALOT of
> performance burden when I could just do something like this:
> SELECT customers.name,
>        customer_revenue.revenue
> FROM   customers
> INNER JOIN customer_revenue
> ON     customers.id = customer_revenue.customer_id
> WHERE  customer_revenue.department = '$department'
> AND    customer_revenue.month = '$month'
> AND    customer_revenue.year  = '$year'
>
> (PHP loop to display all results)
>
>
> That seems like it would perform 20x faster.
>
> It seems that the SQL economies of scale (ability to rapidly slurp large
> sets of data) are completely bypassed when using OOP to view ALOT of
> objects together on one screen.  It seems if I want decent performance I
> would have to ignore OOP rules of method hiding and access the data
> directly when using anything more than just a few objects on one page.
>
>
> A workaround -- I thought that perhaps I could fetch ALL of the data for a
> customer into memory upon initial instantiation, wherein I perform
> something like this: SELECT *
> FROM   customers
> INNER JOIN customer_revenue
> ON     customers.id = customer_revenue.customer_id
> WHERE  customers.id = '$id'
>
> And then use the results from memory as-needed.  It would be all data for a
> customer from all years across all departments.
>
> That might perform better but it'd suck ALOT of data into memory, and what
> if I add more than just revenue to my customer class?  Each customer could
> potentially represent a limitless set of data. So this doesn't seem like an
> optimal solution.
>
>
> * Where am I going wrong?
> * Tell me how YOU fetch data from multiple objects to generate reports.
> * Is this an instance where it's better to just ignore OOP rules and go
> straight to the data? Whew, that's not fun to think about, particularly if
> my queries ever get to be more complex.
>
> Feedback, please.  The goal is to maintain complex queries in just one
> place -- inside a class.  I want my data to _only_ be accessed from the
> black box called an OOP class.
>
> CD
>
> Think you're a good person?  Yeah, right!  ;-)
> Prove it and get ten thousand dollars:
> TenThousandDollarOffer.com

-- 
Larry Garfield			AIM: LOLG42
larry@xxxxxxxxxxxxxxxx		ICQ: 6817012

"If nature has made any one thing less susceptible than all others of 
exclusive property, it is the action of the thinking power called an idea, 
which an individual may exclusively possess as long as he keeps it to 
himself; but the moment it is divulged, it forces itself into the possession 
of every one, and the receiver cannot dispossess himself of it."  -- Thomas 
Jefferson

-- 
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php



[Index of Archives]     [PHP Home]     [Apache Users]     [PHP on Windows]     [Kernel Newbies]     [PHP Install]     [PHP Classes]     [Pear]     [Postgresql]     [Postgresql PHP]     [PHP on Windows]     [PHP Database Programming]     [PHP SOAP]

  Powered by Linux