Memory usage PHP images

Asked

Viewed 1,017 times

8

I have an image upload and to treat the images I’ve used (Imageworkshop and Imagine), however the problem is not the libraries, it is the absurd amount of memory that is used in some situations.

Tested images:

Imagem 01 (1920x1080 1,27MB JPG); 
Imagem 02 (4134x2362 132KB PNG);

I got the following test code for upload (Imageworkshop library is commented):

<?php

require 'vendor/autoload.php';

use PHPImageWorkshop\Core\Exception\ImageWorkshopLayerException;
use PHPImageWorkshop\Core\ImageWorkshopLayer;
use PHPImageWorkshop\Exception\ImageWorkshopException;
use PHPImageWorkshop\ImageWorkshop;
use Imagine\Image\Box;

if (isset($_FILES['imagem'])) {
    echo "Uso de memoria no inicio:".memory_get_usage(true)." bytes <br>";
    $file = $_FILES['imagem'];

    //$imagem = ImageWorkshop::initFromPath($file['tmp_name']);
    //$imagem->save( realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR .'images', sha1(uniqid(rand(), true)) . '.' . explode('/', $file['type'])[1], true, null, 70);
    //$imagemThumb = $imagem;
    //$imagemThumb->resizeInPixel(470, 350, true);
    //$imagemThumb->save(realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'images', sha1(uniqid(rand() , true)) . '_t.' . explode('/', $file['type'])[1], true, null, 70);

    $imagine = new Imagine\Gd\Imagine();

    $savePath = realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR;
    $extension = explode('/', $file['type'])[1];
    $name = sha1(uniqid(rand(), true));
    $options = [];

    switch($extension) {
        case 'png':
            $options['png_compression_level'] = 9;
            break;
        case 'jpg':
        case 'jpeg':
            $options['jpeg_quality'] = 70;
            break;
    }

    $imagine->open($file['tmp_name'])->save($savePath.$name. '.' .$extension, $options)->resize(new Box(470, 350))->save($savePath.$name . '_t' . '.' .$extension, $options);

    echo "Uso de memoria no final:".memory_get_usage(true)." bytes <br>";
    echo "O Pico de memoria:".memory_get_peak_usage(true)." bytes<br>";
}
?>

<html>
<body>
    <form enctype="multipart/form-data" method="POST">
        <input type="file" name="imagem" >
        <input type="submit" value="Enviar">
    </form>
</body>
</html>

Memory result "Image 01":

Uso de memoria no inicio:262144 bytes (0.25 MB)
Uso de memoria no final:12320768 bytes (11.75 MB)
O Pico de memoria:13107200 bytes (12.5 MB)

Memory result "Image 02":

Uso de memoria no inicio:262144 bytes (0.25 MB)
Uso de memoria no final:30670848 bytes (29.25 MB)
O Pico de memoria:90963968 bytes (86.75 MB)

OBS: Of a library for another does not change consumption much, are relatively similar...

I wonder what are the reasons for consuming this absurdly high amount of memory and what I could do to decrease that consumption?

  • If possible, you could formulate an example @Guilhermenascimento ?

  • @Guilhermenascimento What I’m afraid of is, that in tests this happens... and further on there will be many... many requests that can come at the same time and end up bursting for the client... :/ I don’t know if it would really happen, but I want to be cautious of this situation...

  • @Guilhermenascimento Great :) I would like to see an example, when you have a little time... I want to analyze right and try to implement here

1 answer

9


The consumption is high even because in addition to reading the image in memory it is "decoded" and these decoded data also stay in memory, we can say that it is almost impossible to reduce the peak of memory use, but at the end of the process there should probably be a "destruct" method in these classes you used which should facilitate.

Note from PHP5.3 onwards Garbage collector, so he himself manages the use, you will not be able to force release the memory, but manages to facilitate the life of the Garbage collector setando NULL in some variables not used for example. When you have time I may try to formulate a more detailed answer.

Note: A personal experience, the GD Loading the images consumed a lot of memory and images with EXIF (even without change in quality or size) they caused a memory consumption much larger than normal, which often exceeded the memory, causing the following error:

Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 14032 bytes) in /var/www/project/thumb.php on line 10

What I recommend is to do the compression or generate the different types of sizes on the front end using / (and maybe a fallback with ).

Using HTML5 to compress

The browser will have to have support there:

  • Ajax (and today who does not have?)
  • File API (javascript api for data handling/blob, etc)
  • Canvas

Note: I edited the code because the window.atob failed to convert Base64

An example would be something like (I did not test the behavior with "binary" data) this answer from Soen:

Note: The following example uploads and generates a "thumbnail" of the original image

<script type="text/javascript">
function compressImage(file, MAX_WIDTH, MAX_HEIGHT, format, response) {
    var img = document.createElement("img");

    var reader = new FileReader();    
    reader.onload = function(e) {
        img.src = e.target.result;

        var canvas = document.createElement("canvas");
        var ctx = canvas.getContext("2d");

        var width  = img.width;
        var height = img.height;

        if (width > height) {
            if (width > MAX_WIDTH) {
                height *= MAX_WIDTH / width;
                width = MAX_WIDTH;
            }
        } else if (height > MAX_HEIGHT) {
            width *= MAX_HEIGHT / height;
            height = MAX_HEIGHT;
        }

        canvas.width = width;
        canvas.height = height;

        ctx.drawImage(img, 0, 0, width, height);

        response(
            canvas.toDataURL("image/" + format).replace(/^data[:]image\/(.*);base64,/, "")
        );
    };
    reader.readAsDataURL(file);
}

function uploadAjax(data, fileName, success, error)
{
    var oReq = new XMLHttpRequest();

    oReq.open("POST", "upload.php?filename=" + fileName, true);
    oReq.onreadystatechange = function() {
        if (oReq.readyState === 4) {
            if (oReq.status === 200) {
                success(oReq.responseText);
            } else {
                error(oReq.status);
            }
        }
    };
    oReq.send(data);
}

function enviar() {
    var filesToUpload = document.getElementById('input').files;

    //Gerar imagem com tamanho normal
    compressImage(filesToUpload[0], 800, 600, "jpeg", function(resource) {
        uploadAjax(resource, filesToUpload[0].name, function(response) {
            if (response === "OK") {
                alert("sucesso");
            } else {
                alert("Ajax: " + response);
            }
        }, function(errStatus) {
            alert("erro: " + errStatus);
        });
    });

    //Gerar imagem com thumb
    compressImage(filesToUpload[0], 150, 150, "jpeg", function(resource) {
        uploadAjax(resource, filesToUpload[0].name.replace(/\.([a-zA-Z0-9]+)$/, "_thumb.$1"), function(response) {
            if (response === "OK") {
                alert("sucesso");
            } else {
                alert("Ajax: " + response);
            }
        }, function(errStatus) {
            alert("erro: " + errStatus);
        });
    });
}
</script>

<p>
    <input type="file" value="" id="input">
    <input type="button" value="Enviar" id="send">
</p>

<script type="text/javascript">
document.getElementById('send').onclick = enviar;
</script>

PHP must be something like (/a/50982/3635):

<?php
define('PASTA_UPLOAD', '/home/user/projeto/data');

if (isset($_GET['filename']) && $_SERVER['REQUEST_METHOD'] === 'POST') {
    $cl = (int) $_SERVER['CONTENT_LENGTH'];

    $tmpFile = tempnam(sys_get_temp_dir(), '~upload-');

    $file = fopen($tmpFile, 'w');
    $fh   = fopen('php://input', 'r');
    if ($file && $fh) {
        $data = '';
        while (FALSE === feof($fh)) {
            $data .= fgets($fh, 256);
        }
        fwrite($file, base64_decode($data));
    }

    if ($file) {
        fclose($file);
    }

    if ($fh) {
        fclose($fh);
    }

    echo 'OK';
    copy($tmpFile, PASTA_UPLOAD . '/' . $_GET['filename']);
} else {
    echo 'Requisição inválida';
}

I didn’t really have time to test the code, but I believe it works as expected

  • Today, I already use ajax to upload the file, really in the example I did not use because I just wanted to demonstrate the memory overflow... as soon as possible I will use your code and test, thank you. I look forward to your example with File API as well :)

  • @Rafaelwithoeft added example with blob and improved PHP

  • Thanks, still this week I implement your suggestion and notice of the result accepting your reply

  • Hello William, I’m doing some tests here, but I’m having some doubts/ problems, you can help me?

  • @Rafaelwithoeft There is a way to create a chat room here in the question, I think a link appears to you.

  • I created a room, added you there. I do not know how to create a room linked to the question, did not appear the link here...

  • @Rafaelwithoeft I thought I could create directly linked to the question, I’m in the room. Link for other users see: http://chat.stackexchange.com/rooms/23776/room-for-rafael-withoeft-and-guilherme-nascimento

  • Sometimes a link appears, this time there was... I do not know how to create a room there linking the question here... Should have right?

Show 3 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.