Forum Moderators: coopster

Message Too Old, No Replies

PHP memory leaks?

         

cantona

2:59 pm on Sep 8, 2009 (gmt 0)

10+ Year Member



Hi

I'm having problems with my PHP script eating up memory. I'm not sure if it's because of my code or an actual PHP bug (some have been reported in the past).

Error:


Fatal error: Allowed memory size of 104857600 bytes exhausted (tried to allocate 700161 bytes) in #*$!#*$!x on line 168

This is even with me setting:


ini_set("max_execution_time",120);
ini_set("memory_limit","12M");

My PHP version is: 5.2.4

The script involves a php file which does a simple SQL query.
A class instance is created.
Then there is a while loop going over the record set and passing a value to the class methodA.
Class methodA runs another query, loops andcreates an array, then passes the array to methodB.
methodB then loops over the array, running a query, connecting and disconnecting from the db each time and passes back to methodA.

From what I can gather, most of the memory is being used up by the loops in the php file and in the class. Say at a rate of 1MB per 8 database rows returned. So it's going over the 12MB limit pretty quickly.

I have found some articles relating to memory leaks in php5.1, but we have 5.2 and they should have been fixed by then.

[bugs.php.net...]
[bugs.php.net...]
[webmasterworld.com...]

I've looked into using unset() and avoiding looping in the fashion they state, but it only reduces it by a few MBs, nothing major.

Have you any ideas what I can do to reduce the memory usage of the script?

I appreciate any pointers you can think of.

[edited by: eelixduppy at 3:01 pm (utc) on Sep. 9, 2009]
[edit reason] snipped some links [/edit]

Tommybs

4:01 pm on Sep 8, 2009 (gmt 0)

10+ Year Member



Would it be possible for you to post more of your script? It just helps to visualize the loops and see how nested they are

thanks

CyBerAliEn

9:33 pm on Sep 8, 2009 (gmt 0)

10+ Year Member



I would bet it is more the structure of your code and not a PHP issue.

I had a situation where I read a text file (thousands of lines of data, only a few megabytes in size). My script read the file, and processed the data through a series of loops... creating arrays... etc. End result? Simply processing that ~3mb text file into a few arrays ended up needing something like 100mb of memory. I learned after doing this, that creating an array to hold data... though being incredibly useful, easy to deal with, manipulate, etc... it actually consumes a lot more memory than you'd expect!

I would scrutinize the process of what it is you are trying to do. Most likely (without seeing your code), you might be creating arrays that are being stored in memory with each loop (which might not be needed). A solution to this could be to simply discard (unset/etc) any arrays that are created and not needed. Remember that PHP will create "new memory space" for each new instantiation of a variable/object; PHP does not inherently do "references"; and it will keep these items in memory until told otherwise or the script terminates. It is also possible that the total memory used by your array could very well exceed the memory limit (if huge amount of data).

Obviously, I cannot see your code... nor do I have the time to trace it... but it is obvious something in your loops is probably getting kept and increasing memory with each loop.

rocknbil

3:30 am on Sep 9, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Or, poor selects/database structure, either is speculation without seeing the problem area of your code.

cantona

9:23 am on Sep 9, 2009 (gmt 0)

10+ Year Member



Thanks for the feedback guys. I think CyBerAliEn is on the right lines with this - it matches to what I'm reading elsewhere. The thing is, I've started unsetting arrays now and it's still not making much difference.

Here is my code:

The PHP file is looping through a list of companies that distribute in several locations. It's passing the company ID to a class that will return a list of all the location names that that company distributes to.

PHP file (memory is ok until this point. It's this line that racks it up about 1MB every 15 loops):


$fullLocationList = $cd->loadLocationByCompanyId($col['CompanyId']);

Class:


<?PHP

class CompanyDistribution {

# run queries on db
private function runQuery($sql)
{
$dbc = DBConnect(DB_DSN_X,true,true);
$qry = DBQuery($sql, $dbc, false);
DBDisconnect($dbc);
unset($dbc);
unset($sql);
return $qry;
}

# return all locations a company distributes to
public function loadLocationByCompanyId($mid)
{
$sql ="SELECT LocationCode FROM CompanyDistribution WHERE CompanyId = {$mid} ";
$qry = $this->runQuery($sql);
$locationCodes=array();
while($row = DBFetchArray($qry))
{
$locationCodes[] = $row['LocationCode'];
}
$companyLocationNames = $this->loadLocationByCode($locationCodes);
unset($qry);
unset($locationCodes);
return $companyLocationNames;
}

# return location name from location code
# this is also used directly by the PHP file
public function loadLocationByCode($locationCodes)
{
$locationNames = array();
foreach($locationCodes as $x=>$code)
{
$sql = "SELECT LocationLevelName AS LocationName
FROM LocationLevel
WHERE LocationLevelCode = '{$code}'
";
$qry = $this->runQuery($sql);
while($row = DBFetchArray($qry))
{
$locationNames[$code] = $row['LocationName'];
}
}
unset($locationCodes);
return $locationNames;
}

}

I've been on this another few hours this morning, but whatever I try I'm hitting the error. My memory limit is currently 16MB, but I had it at 120MB and it still broke. My search is currently returning 450 rows, but the app is supposed to return up to 2500....... eek!

omoutop

9:38 am on Sep 9, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



each company has a large number of area codes and for each are code you execute a query with some results.
this is stressfull - i think that each company has a very large number of area codes assosiated with them.

try creating some indexes in yot databse tables to speed up the things a little

foreach($locationCodes as $x=>$code)
{
$sql = "SELECT LocationLevelName AS LocationName FROM LocationLevel WHERE LocationLevelCode = '{$code}' ";
$qry = $this->runQuery($sql);
while($row = DBFetchArray($qry))
{
$locationNames[$code] = $row['LocationName'];
}

unset($locationCodes);
return $locationNames;
}

Hve you tried altering your logic here?
Perhaps you can construct your sql like :
SELECT LocationLevelName AS LocationName
FROM LocationLevel WHERE LocationLevelCode IN '$code_list_of_id'
if you can of course (where $code_list_of_id would be something like $code_list_of_id='9,12,456,32,123' - comma seperated list of ids

Dinkar

3:03 pm on Sep 9, 2009 (gmt 0)

10+ Year Member



What do you do by returning the company location names? Display it, right? Then why don't you display the data in that function itself instead of creating an array and returning it? Try this and let us know if it helps.

CyBerAliEn

4:59 pm on Sep 9, 2009 (gmt 0)

10+ Year Member



Hmmm....

Your "process" is...

Your process seems to be...
- Call 'loadLocationByCompanyId'. This then...
- runs a query;
- creates an array 'locationCodes';
- runs a loop thru database results (temporarily creates an array for each DB result);
- then call 'loadLocationByCode' inputting array 'locationCodes';
- + this then runs a loop on locationCodes
- + - runs an SQL query for each location code
- + - then loops each of these query results
- + - + sets up array 'locationNames' with the name from the result array
- + - returns the location names array
- original method call then returns this same names array

Not overly complicated. But consider if you had hundreds/thousands of companies with hundreds/thousands of location codes. You end up nesting a few arrays/loops/queries that really isn't as efficient as it could be.

As suggested above, you could save A LOT of processing (and memory) by passing the work on to SQL. It seems to me, you could run a single query using JOINS or such to retrieve what you want, instead of running it as you do. I haven't done a lot of SQL lately, but given the 2-table design you have, this should be feasible I imagine. (join the tables, and query what you need from the combined result; etc)

cantona

10:56 am on Sep 10, 2009 (gmt 0)

10+ Year Member



Hi guys, thanks for the help with this.

I re-structures my SQL with joins, that helped.

If the class structure is "not awful" then then problem must be due to the amount of rows returned.

I'm displaying memory_get_usage() after all the SQL and class sections and it's only 8MB.

But then I'm displaying 2500 rows on on page and each row is using 10KB... so I think this is just due to the amount of rows returned.

I've increased my memory_limit to 128MB and that's helped a lot. It just takes ages to load the page (if hitting 2k+ rows). But I suppose there's not much can be done about that unless I return fewer rows on different pages.

brotherhood of LAN

11:38 am on Sep 10, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



You may want to look at shmop functions too, arrays in PHP take up more memory than you might have expected. Useful if you're in a constrained shared server environment. Doing the work in MySQL would seem to be the easiest option though, and sometimes using an unbuffered query [us2.php.net] can be beneficial outside of your nested queries.

shmop functions [php.net]
thread on bugs.php.net about array sizes [bugs.php.net]

Dinkar

12:34 pm on Sep 10, 2009 (gmt 0)

10+ Year Member



Use paging. 10 to 50 rows per page max. Also use cache. These techniques are very helpful to reduce server load.