gd-png 无法分配图像数据

Posted

技术标签:

【中文标题】gd-png 无法分配图像数据【英文标题】:gd-png cannot allocate image data 【发布时间】:2015-08-05 23:21:42 【问题描述】:

我有一个广泛使用 GD 的旧代码库(不,切换到 imagemagick 不是一种选择)。它已经运行了好几年,通过了几个版本。但是,当我在当前的开发环境中运行它时,我遇到了一个神秘的 gd-png 错误:调用imagecreatefrompng() 时无法分配图像数据错误。我使用的PNG文件和我一直使用的一样,所以我知道它可以工作。

当前设置:

Ansible-provisioned vagrant box  
Ubuntu 14.04  
php 5.5.9  
GD 2.1.1  
libPNG 1.2.50

脚本运行时 PHP 的内存限制为 650M,但最终杀死脚本的是内核本身,更改 PHP 的内存限制似乎没有效果。

图像尺寸为 7200x6600,磁盘大小约为 500KiB。这在我的其他环境中不是问题,只是在我的开发环境中新出现。不幸的是,我无法再访问其他环境来进行比较,尽管设置与上一个工作环境相似——Ubuntu 14.04、PHP 5.5、足够的内存分配。

在此设置中可能会发生什么在我之前的设置中没有发生的情况?我该如何解决这个问题?

【问题讨论】:

它适用于哪个较低的分辨率?类似问题(唯一的答案与内存限制有关):github.com/claviska/SimpleImage/issues/81您可以尝试计算$required_memory = Round($width * $height * $size['bits']);所需的内存 如果你用一行var_dump(imagecreatefrompng($file))创建一个空白脚本你会得到什么? ulimit -v 报告什么? VM 有多少内存可用? 您提到了it's ultimately the kernel itself that ends up killing the script,那么您确定系统上有足够的内存吗?如果确实是内核杀死了脚本并且您已将其登录到dmesg,那么您应该增加服务器的内存。 7200x6600 考虑到每个组件的 1 个字节 (rgb+a) 大约是 190M 未压缩。内核通常只有在内存不足时才会杀死进程 【参考方案1】:

我浏览了一下 PHP 5.5.9 源代码,试图找到您的特定错误字符串“无法分配图像数据”,但我能找到的最接近的是“gd-png 错误:无法分配 gdImage 结构”。用于 PHP 5.5.9(一直到 7.0.0)的 GD 捆绑版本是 2.0.35 版本,因此您似乎正在查看自定义构建(?)。在这里窃听 PHP 人员可能无济于事。

查看GD源码(2.1.2,好像找不到2.1.1),唯一出现这个错误的地方是: https://github.com/libgd/libgd/blob/master/src/gd_png.c#L435

image_data = (png_bytep) gdMalloc (rowbytes * height);
if (!image_data) 
    gd_error("gd-png error: cannot allocate image data\n");
    png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
    if (im) 
        gdImageDestroy(im);
    
    if (palette_allocated) 
        gdFree (palette);
    
    return NULL;

gdMalloc 在哪里:

https://github.com/libgd/libgd/blob/GD-2.1/src/gdhelpers.c#L73

 void *
 gdMalloc (size_t size)
 
      return malloc (size);
 

恐怕这就是我的侦探工作。据我所知,PHP 中捆绑的 2.0.35 GD 版本也只是使用 malloc,所以乍一看并没有真正的区别。我还尝试在捆绑版本中找到一些等效的代码,但到目前为止我还没有找到它,它似乎在这里:

https://github.com/php/php-src/blob/PHP-5.5.9/ext/gd/libgd/gd_png.c#L323

 image_data = (png_bytep) safe_emalloc(rowbytes, height, 0);

我似乎找不到 safe_emalloc,但似乎旧的 PHP 捆绑版本在这里使用的内存分配与您的环境使用的版本不同。 也许你最好的选择是与 GD 开发人员核实?。为避免徒劳无功,我认为尝试另一个环境是您最好的选择——在确认他们确实使用的是非标准 GD 版本之后。

在更多的 PHP 源代码 safari 之后,似乎在所有扩展中都使用了 safe_emalloc,所以我猜(猜你猜)这是为 PHP 扩展分配内存的首选方式(looks like it)。如果您的环境实际上使用的是未更改的 GD 2.1.1,则很可能它忽略了任何 PHP 内存设置/限制。您也许可以找到其他指定(“独立”)GD 内存限制的方法?

编辑:查看official libgd faq,在底部附近,它指出 PHP 扩展应该确实遵守内存限制,它指定 8000x8000 像素的图像将大约需要 256MB,因此您的 650MB 限制应该足够了(除非您在同一运行中处理多个副本?)并且它不起作用的事实证实了内存分配发生了一些可疑的事情。

【讨论】:

so it seems you're looking at a custom build(?) 不,在 Ubuntu 14.04 存储库中存储 php5-gd 包。 @Shauna 这很奇怪,检查bazaar.launchpad.net/~ubuntu-branches/ubuntu/trusty/php5/trusty/… 的 Ubuntu 14.04 源代码表明它也必须使用 2.0.35。如果您认为这没有朝着正确的方向发展,我不会再争论了,但是很好奇您最终是如何获得 GD 2.1.1 的。毕竟,非标准环境将是非标准行为的最佳解释。 看来版本报告可能有误。 phpinfo()(我在问题中得到信息的地方)说 GD 的版本是 2.1.1(最终版本在 1 月发布),但 apt-cache policy libgd3 说它是 2.1.0-3(这是 Ubuntu 附带的)。 所以,另一个有趣的发现 - 执行任务的脚本将 memory_limit 设置为 650M,但 VM 配置为 512M,这显然是一个问题。奇怪的是,这仍然与旧 VM 一致(两者的设置也相同),从未失败。【参考方案2】:

如果我是你,我会这样做:

    向 php-devel 列表发送消息,看看它是否是一个已知问题。您还需要搜索他们的错误跟踪器。 http://php.net/mailing-lists.php 和 https://bugs.php.net/ 使用 GD 命令行实用程序查看是否可以使用 cmd 行内容处理具有相同版本的相同映像。这是试图确定是 GD 和图像的问题,还是 PHP-gd 库或某种组合的问题。

1.a 创建一个 PHP CLI 程序来调整图像大小并查看它是否有效

    弄清楚当您在底层(可能是 c)代码中调用 php 函数时会发生什么。这将涉及将源下载到 php-gd 模块并查看它。 编写一个与底层 PHP-gd 库执行相同操作的最小 c 应用程序,看看是否可以重现错误

里面的东西应该能准确地告诉你发生了什么,尽管这需要一些工作。您应该能够为您的环境获取所有工具/资源。开源之美,对吧?

另一种选择是在您的环境中尝试这些应用程序的不同版本,看看是否能找到一个可行的。这是“霰弹枪”方法,而不是最好的,IMO。

【讨论】:

【参考方案3】:

根据我的经验:

尝试使用不同行大小的文件。我遇到了另一个图像库的问题,它读取的图像长度不超过 3000 像素/行。否则,它的绝对大小不受限制。 你有一个相当大的图像,如果它在内存中的 RGBA 中,你最终会得到 180M 未压缩的图像数据,RGB 上 140M,仍然是 45M 作为 8Bit。您确定加载时不会超出您的进程限制?

【讨论】:

我已经在运行相同操作系统、相同版本 php 和相同内存限制且没有问题的 puppet 提供的虚拟机上使用相同的图像几个月了,这让我感到困惑.【参考方案4】:

您在相同的库中使用了相同的图像,这意味着libgdlibpng 对内存大小没有限制(或者至少对您使用的大小没有限制),问题可能是 php 但您确定内存限制为650M(如果您的php 安装使用suhosin,您可能需要检查suhosin.memory_limit)

你告诉内核正在杀死脚本(但我不知道你为什么这么说,是否有一个dmesg 日志?)但是内核只有在内存不足时才会杀死一个进程。

一个问题可能是内存碎片 / 连续分配,现在它应该更像是一个内核空间问题而不是用户空间,但您的脚本分配的大小不是每天的大小。 如果您的脚本在正常运行时间较长的服务器上运行并且有大量进程分配和释放内存,那么碎片可能是一个问题,如果是您刚刚启动的虚拟机并且脚本在第一次启动时失败,那么问题就出在不要碎片化。

您可以在下面做一个简单的测试、命令和代码,它使用gcc 编译器创建(在您启动命令的目录中)一个a.out 可执行文件,该可执行文件分配并设置为0 大小的内存块191 x 1024 x 1024(约~190M)

echo '#include <stdlib.h>\n#include <string.h>\n#define SIZ 191*1024*1024\nint main() void *p=malloc(SIZ);memset(p,0,SIZ);return (p==NULL?1:0);' \
| gcc -O2 -xc -

运行可执行文件:

./a.out

如果问题是系统内存,内核应该杀死可执行文件(我不确定 100%,内核有一些关于在没有内存时杀死哪个进程的策略,待检查)

您应该会看到终止消息,可能是dmesg

(我认为这不应该发生,而是 malloc 应该失败)

如果malloc成功,可执行文件可以分配足够的内存,它应该返回0,如果失败应该返回1

验证返回码只需使用

./a.out 
echo $? 

(中间不要执行任何其他命令)

如果malloc 失败,您可能只需要向系统添加物理或虚拟内存。

请记住,大约 190M 只是一张未压缩图像的内存,这取决于 phplibgdlibpng 的工作方式,整个过程的内存甚至可能是两倍(或更多)。

我做了一个简单的测试并分析了内存使用情况,我的系统上图像 7600x2200(磁盘上 1.5M)的峰值约为 342M,这是测试:

<?php
$im     = imagecreatefrompng("input.png");
$string = "Sinker sucker socks pants";
$orange = imagecolorallocate($im, 220, 210, 60);
$px     = (imagesx($im) - 7.5 * strlen($string)) / 2;
imagestring($im, 3, $px, 9, $string, $orange);
imagepng($im);
imagedestroy($im);

我一开始尝试使用 xhprof,但它返回的值太低。

所以我尝试了一个简单的脚本memusg

memusg /usr/bin/php -f test.php >output.png

(顺便说一句,测试工作)

【讨论】:

【参考方案5】:

我昨天遇到了这个问题,今天解决了。

昨天的环境:

    php-7.0.12 libpng-1.6.26 libgd-2.1.1

当我调整 png 图像的大小时,这个套件会崩溃。 经过内存检查,我认为这可能是最新的 php 或 libpng 中的错误。 所以我将环境更改为:

    php-5.6.27 libpng-1.2.56 libgd-2.1.1

我将 php 和 libpng 更改为成熟版本,可以长期使用。

重新编译并重新安装这些 - 它适用于 png 和 jpeg。

【讨论】:

以上是关于gd-png 无法分配图像数据的主要内容,如果未能解决你的问题,请参考以下文章

无法分配 ndarray

将 createObjectURL 的结果分配给图像无法在移动 Safari 上加载资源

解决ubuntu18.04安装nvidia驱动报nvidia-dkms依赖无法安装(全程配图)

无法从 django 服务器中的数据库上传图像

TCP协议中的三次握手和四次挥手

TCP协议中的三次握手和四次挥手(图解)