1

I am using this library to work with fractions in PHP. This works fine but sometimes, I have to loop over a lot of values and this results in the following error:

Allowed memory size of 134217728 bytes exhausted

I can allocate more memory using PHP ini but that is a slippery slope. At some point, I am going to run out of memory when the loops are big enough.

Here is my current code:

for($q = 10; $q <= 20; $q++) {
    for($r= 10; $r <= 20; $r++) {
        for($p = 10; $p <= 20; $p++) {
            for($s = 10; $s <= 20; $s++) {
                for($x = 50; $x <= 100; $x++) {
                    for($y = 50; $y <= 100; $y++) {

                        $den = ($q + $r + 1000) - ($p + $s);
                        $num = $x + $y;
                        $c_diff = new Fraction($num, $den);
                    }
                }
            }
        }
    }
}

I used memory_get_peak_usage(true)/(1024*1024) to keep track of the memory the script is using. The total memory used was just 2MB until I added the line that creates a new fraction.

Could anyone please guide me on how to get rid of this error. I went through the code of the library posted on GitHub here but can't figure out how to get rid of the exhausted memory error. Is this because of the static keyword? I am beginner so I am not entirely sure what's going on.

The library code is about a 100 lines after removing the empty lines and comments. Any help would be highly appreciated.

UPDATE:

  1. The script exhausts its memory even if I use just this block of code and nothing else. I definitely know that creating a new Fraction object is the cause of exhausting memory.
  2. I thought that there was not need to unset() anything because the same one variable to store the new fractional value over and over again.
  3. This leads me to think that whenever I creating a new Fraction object something else happens which in the library code that takes up memory which is not released on rewriting the value in the $c_diff variable.
  4. I am not very good at this so I thought it has something to do with the static keyword used at a couple of places. Could anyone confirm it for me?

If this issue can indeed be resolved by using unset(), should I place it at the end of the loop?

iKnowNothing
  • 175
  • 11
  • how about `ini_set('memory_limit', '140M')` ? –  Jun 16 '18 at 03:49
  • As I said, I will run out of memory if the loop gets bigger. In one case, I allocated up to 1GB of memory and still ran out. :) – iKnowNothing Jun 16 '18 at 03:51
  • I first place, why do you need that **nasty loop**? –  Jun 16 '18 at 03:52
  • I have to check which values produce a particular fraction. – iKnowNothing Jun 16 '18 at 04:04
  • What version of PHP are you running? – Martin Jun 16 '18 at 12:08
  • @Martin PHP 7.1.2 :) – iKnowNothing Jun 16 '18 at 12:08
  • Can you show us your Fraction class? – Martin Jun 16 '18 at 12:09
  • This is from a library on GitHub https://github.com/phospr/fraction/blob/master/src/Fraction.php. :) – iKnowNothing Jun 16 '18 at 12:09
  • Please copy/paste it into your question – Martin Jun 16 '18 at 12:09
  • It is about 200 to 300 lines. Won't Stack Overflow complain that there is a lot of code? – iKnowNothing Jun 16 '18 at 12:11
  • Also: 3 of your `for` loops are worthless : because your values are summed; you need to only test values `$p + $s`, `$q + $r`, and `$x + $y` . You are running a huge amount of repetitions on your processing. – Martin Jun 16 '18 at 12:11
  • Cut down the code, remove the stuff that's not relevant to your question. – Martin Jun 16 '18 at 12:11
  • You should not need to instantiate a `new` class every iteration; simply add new values to the existing class..... this is a big server overhead making hundreds of classes rather than simply reusing the same class hundreds of times. – Martin Jun 16 '18 at 12:13
  • I need the value of all these six variables. :) That's why I loop over them all instead of directly looping over their sum. – iKnowNothing Jun 16 '18 at 12:14
  • but if you get a result with `$p = 13` and `$s = 11` this result is exactly the same as if `$p = 10` and `$s = 14` -- so you have what looks like 50% calculation repetition. – Martin Jun 16 '18 at 12:18
  • 1
    I would **highly** recommend you stop using PHP and instead explore [Matlab](https://www.mathworks.com/products/matlab.html) or [Haskell](https://www.haskell.org/). – Martin Jun 16 '18 at 12:20
  • @Martin but I need both these sets of values. :) I need to know that the condition I am looking for is true for both `(p, s)` at `(13, 11)` and `(10, 14)`. Is there any other way to arrive at these values instead of looping? – iKnowNothing Jun 16 '18 at 12:31
  • 1
    All you need is the sum; so once you've found that the value `24` works; you can find all the parts (over 10) that fit that value: ie `(24 (max) - 10 (min) = 14)`, so there are `10,14`, `11,13` , `12,12`, `13,11`, `14,10` valid values. savng yourself 80+ processing work. for that for loop already. You maybe only need to do 10% of the work your `for` loops are currently doing. – Martin Jun 16 '18 at 12:35
  • Thanks @Martin I will look into that. Also, thanks for your advice about not creating a new class after every iteration. The library did not have any way to set the value of numerator and denominator once the `Fraction` had been created. I have added my own method to it and it seems to be working fine now. :) – iKnowNothing Jun 16 '18 at 12:40
  • The library looks very inefficient. For what you need I suggest you simply dig out a simpler process as outlined in my answer. – Martin Jun 16 '18 at 12:43
  • Thanks for the suggestion about Matlab @Martin. Matlab and Mathematica can do a lot of stuff that I wanted to do without any problem. :) – iKnowNothing Jul 07 '18 at 17:51

3 Answers3

3

Various Possible fixes and efficiencies:

You have 6 for loops, each loop cycles a single integer value within various ranges.

But your calculation only uses 3 values and so it doesn't matter if $p = 10; $s = 14; or $p = 13; $s = 11; These are entirely equivilant in the calculation.

All you need is the sum; so once you've found that the value 24 works; you can find all the parts (over the minimum value of 10) that fit that value: ie (24 (sum) - 10 (min) = 14), then collect the values within the range; so there are 10,14, 11,13 , 12,12, 13,11, 14,10 valid values. savng yourself 80%+ processing work on the inner for loops.

 $pairs = "p,s<BR>"; //the set of paired values found
 $other = $sum - $min;
     if($other > $max){
        $other = $sum - $max;
     }
     $hardMin = $min;
     while ($other >= $hardMin && $min >= $hardMin && $min <= $max){
         $pairs .= $min.", ".$other."<BR>";
         $other--; // -1
         $min++;  // +1
     }
   
  print $pairs; 
  

Giving:

  p,s
10,14
11,13
12,12
13,11
14,10

So for this for loop already, you may only need to do ~10% of the total work cycling the inner loops.


Stop instantiating new classes. Creating a class is expensive. Instad you create one class and simply plug the values in:

Example:

$c_diff = new Fraction();

for(...){
    for(...){
        $c_diff->checkValuesOrWhateverMethod($num, $den)
    }
}

This will save you significant overhead (depending on the structure of the class)


The code you linked on GitHub is simply to turn the value into a fraction and seems to be highly inefficient.

All you need is this:

function float2frac($n, $tolerance = 1.e-6) {
    $h1=1; $h2=0;
    $k1=0; $k2=1;
    $b = 1/$n;
    do {
        $b = 1/$b;
        $a = floor($b);
        $aux = $h1; $h1 = $a*$h1+$h2; $h2 = $aux;
        $aux = $k1; $k1 = $a*$k1+$k2; $k2 = $aux;
        $b = $b-$a;
    } while (abs($n-$h1/$k1) > $n*$tolerance);

    return $h1."/".$k1;
}

Taken from this excellent answer.

Example:

for(...){
    for(...){
        $den = ($q + $r + 1000) - ($p + $s);
        $num = $x + $y;
        $value = $num/den;

        $c_diff = float2frac($value);
        unset($value,den,$num);
    }
}

If you need more precision you can read this question and update PHP.ini as appropriate, but personally I would recommend you use more specialist maths languages such as Matlab or Haskell.


Putting it all together:

  • You want to check three values, and then find the equivilant part of each one.
  • You want to simply find the lowest common denominator fraction (I think).

So:

/***   
 * to generate a fraction with Lowest Common Denominator
 ***/
function float2frac($n, $tolerance = 1.e-6) {
    $h1=1; $h2=0;
    $k1=0; $k2=1;
    $b = 1/$n;
    do {
        $b = 1/$b;
        $a = floor($b);
        $aux = $h1; $h1 = $a*$h1+$h2; $h2 = $aux;
        $aux = $k1; $k1 = $a*$k1+$k2; $k2 = $aux;
        $b = $b-$a;
    } while (abs($n-$h1/$k1) > $n*$tolerance);

    return $h1."/".$k1;
}

 /***
  * To find equivilants
  ***/
 function find_equivs($sum = 1, $min = 1, $max = 2){
    $value_A = $sum - $min;
    $value_B = $min;
    if($value_A > $max){
        $value_B = $sum - $max;
        $value_A = $max;
    }
    $output = "";
    while ($value_A >= $min && $value_B <= $max){
        if($value_A + $value_B == $sum){
            $output .= $value_A . ", " . $value_B . "<BR>";
        }
        $value_A--; // -1
        $value_B++;  // +1
    }
    return $output;
}

 /***
  * Script...
  ***/     
 $c_diff = []; // an array of results. 
 for($qr = 20; $qr <= 40; $qr++) {
     for($ps = 20; $ps <= 40; $ps++) {
         for($xy = 100; $x <= 200; $xy++) {
              $den = ($qr + 1000) - $ps;
              $num = $xy;
              $value = $num/$den; // decimalised  
              $c_diff[] = float2frac($num, $den);
              /*** 
               What is your criteria for success?
               ***/ 
              if(success){
                 $qr_text = "Q,R<BR>";
                 $qr_text .= find_equivs($qr,10,20);
                 $sp_text = "S,P<BR>";
                 $sp_text .= find_equivs($sp,10,20);
                 $xy_text = "X,Y<BR>"; 
                 $xy_text .= find_equivs($sp,50,100);
               }
             }
         }
      }

 
  • This should do only a small percentage of the original looping.
Community
  • 1
  • 1
Martin
  • 22,212
  • 11
  • 70
  • 132
  • 1
    I was thinking the same thing about inefficiency of the original code. I am not familiar with the original library being used but a default time complexity for a simple use case being O(6) is quite massive. I am not surprised memory issues are becoming an issue. – ViaTech Jun 16 '18 at 12:49
  • Thank you for the insights Martin. :) – iKnowNothing Jun 16 '18 at 13:01
  • @iKnowNothing I have updated my answer code, please take a look – Martin Jun 16 '18 at 13:03
  • @Martin could you please clarify one more thing for me? Even though I was creating a new Fraction with each loop, I was storing it in the same old variable. So, why did I exhaust all my memory? – iKnowNothing Jun 16 '18 at 13:04
  • The memory was being exhausted by the *class* not by your variables. – Martin Jun 16 '18 at 13:04
  • I thought the old class instance was being destroyed after each reassignment. :) Thanks for the clarification Martin. I learned a lot of new things today. – iKnowNothing Jun 16 '18 at 13:06
  • 1
    @iKnowNothing I made final fixes to the equivilant function in the bottom code. – Martin Jun 16 '18 at 13:36
1

I guess this isn't the entire block of code you are using.

This loop creates 50*50*10*10*10*10 = 25.000.000 Fraction objects. Consider using PHP's unset() to clean up memory, since you are allocating memory to create objects, but you never free it up.

editing for clarification

When you create anything in PHP, be it variable, array, object, etc. PHP allocates memory to store it and usually, the allocated memory is freed when script execution ends.

unset() is the way to tell PHP, "hey, I don't need this anymore. Can you, pretty please, free up the memory it takes?". PHP takes this into consideration and frees up the memory, when its garbage collector runs.

It is better to prevent memory exhaustion rather than feeding your script with more memory.

icydemon
  • 78
  • 1
  • 9
  • Thank you for the answer icydemon. I really appreciate it. :) I am adding more details in the question because adding it in comments would make it unreadable. – iKnowNothing Jun 16 '18 at 11:59
0

Allowed memory size of 134217728 bytes exhausted

134217728 bytes = 134.218 megabytes

Can you try this?

ini_set('memory_limit', '140M')
/* loop code below */
  • *"I can allocate more memory using PHP ini but that is a slippery slope. At some point, I am going to run out of memory when the loops are big enough."* – Martin Jun 16 '18 at 12:07