Re: [PHP] OOP slow -- am I an idiot?



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
.



Relevant Pages

  • Sub Query Consolidation, Syntax Question
    ... I am new to writing free hand SQL, and have a two part question. ... #1 I'm trying to consolidate a number of queries into one query. ... to me that there should be a way to consolidate this into one place, ... Information] RIGHT JOIN NetShipPlusOpen ON [Customer Information].[Sold To ...
    (microsoft.public.access.queries)
  • Re: comboBox & northwinds sample orders form
    ... True, but OFTEN to display a customer name when you have a customer id, we ... dlookup) are suggest. ... the problem is that you don't need dlookup in a query. ... You can always just shove in the sql in place of the ...
    (microsoft.public.access.formscoding)
  • Re: Query going wrong
    ... statement from the Query. ... I am not a big programmer so I have no clue what it means when it's in SQL ... >> When I goto the Report Query Report it prints all the same information for ... >> Then when I am in a query where one customer has had the same service ...
    (microsoft.public.access.queries)
  • Re: SQL Syntax Help Please
    ... I would just have another Query that did the sums for the amount recieved ... The outer "layer" is your basic sql statement to get the total for the Order ... What makes this work for this grouping, so you have the Payments ... is the inner part that sums the customer payments based on the ...
    (microsoft.public.access.tablesdbdesign)
  • OOP slow -- am I an idiot?
    ... I think perhaps I'm using classes and OOP incorrectly. ... I want to create a "customer" class which fetches its attributes from a MySQL database. ... Perform a query for the first department ... upon initial instantiation, wherein I perform something like this: ...
    (php.general)