一、场景
领导:小A同学,我们要做一个样本上传进行分析的功能,你看下是否使用base64编码加进去,这样客户端的同学就不需要用form-data方式来上传了,直接使用json格式就可以上报,可以让格式上报统一。
小A:好的,领导,马上搞定!
咋看上面的对话没啥问题,很多公司团队内部为了一些标准化的问题,都会进行一些技术选型问题,但是噩梦也就从这个对话开始,功能实现当然都是很简单的,先来看简单流程图:
本身的流程是一个很简单的文件转换成base64上传,再服务端decode保存,在开发联调过程中没有问题,非常完美的走下去了。
二、问题来了
突然有一天终端同学误操作将一个37M文件上传,nginx与php-fpm文件上传限制均为(60M),但是在界面出现500错误,进入docker 日志查看有一条数据:
Allowed memory size of 8388608 bytes exhausted (tried to allocate 1298358 bytes)
玩php的基本都知道这是啥意思,就是代码运行过程中使用内存超过 我们php.ini设置的memory_limit 的值,然后就屁颠屁颠进入php.ini找参数配置,很快找到:
memory_limit=128M
然后就转念一想,不应该出现这个问题,我们知道,php的内部变量使用cow(写时复制)机制来实现,那么内存申请只有在变量赋值变更才会进行
三、测验
接下来我们单独写一个程序来进行测试,将一个4.89M文件进行base64_encode 编码 与base64_decode解码,查看各自占用内存以及过程中占用峰值内存
<?php $mid = memory_get_usage(); $apk_content = file_get_contents(__DIR__ . '/4bc1c8a05b8505662be778b6dad23b55.apk'); var_dump('文件加载到内存:' . round((memory_get_usage() - $mid) / 1024 / 1024, 2) . 'M'); var_dump('过程中峰值使用的内存:' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'M'); unset($mid); $mid = memory_get_usage(); $base64_encode = base64_encode($apk_content);unset($apk_content); var_dump('base64_encode占用内存:' . round((memory_get_usage() - $mid) / 1024 / 1024, 2) . 'M'); var_dump('过程中峰值使用的内存:' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'M'); unset($mid); $mid = memory_get_usage(); base64_decode($base64_encode); var_dump('base64_decode占用内存:' . round((memory_get_usage() - $mid) / 1024 / 1024, 2) . 'M'); var_dump('过程中峰值使用的内存:' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'M'); unset($mid);
执行结果:
string(29) "文件加载到内存:4.89M"
string(38) "过程中峰值使用的内存:5.25M"
string(33) "base64_encode占用内存:1.63M"
string(39) "过程中峰值使用的内存:11.76M"
string(30) "base64_decode占用内存:0M"
string(38) "过程中峰值使用的内存:13.4M"
通过上面结果可以看出
四、源码解析
base64_encode源码解析
首先找到对应的c文件 base64.c,找到里面php_base64_encode函数
PHPAPI zend_string *php_base64_encode(const unsigned char *str, size_t length) /* {{{ */ { const unsigned char *current = str; unsigned char *p; zend_string *result; result = zend_string_safe_alloc(((length + 2) / 3), 4 * sizeof(char), 0, 0); p = (unsigned char *)ZSTR_VAL(result); ... }
我们先来分析这段代码,因为这里涉及到内存的问题,那么我们就看
result = zend_string_safe_alloc(((length + 2) / 3), 4 * sizeof(char), 0, 0);
这啥意思呢?
申请内存,最终调用的函数是:
safe_emalloc(size_t nmemb, size_t size, size_t offset)
在wiki上解释是:
void *safe_emalloc(size_t nmemb, size_t size, size_t offset)分配缓冲区来存放每块大小为 size 字节的 nmemb 块,并附加 offset 字节。类似于 emalloc(nmemb * size + offset),但增加了针对溢出的特殊保护。
那么我可以简单的认为,就是在encode过程中,重新申请了内存,申请的内存大小是文件本身的 4/3 大小,加上原来的文件本身大小,那么峰值大小可以理解为
峰值内存= 7/3 *4.89 = 11.41
那么与我们实验过程中峰值大小基本是相符。
base64_decode操作
同样我们进行源码分析
PHPAPI zend_string *php_base64_decode_ex(const unsigned char *str, size_t length, zend_bool strict) /* {{{ */ { const unsigned char *current = str; int ch, i = 0, j = 0, padding = 0; zend_string *result; result = zend_string_alloc(length, 0); ... }
这里使用的zend_string_alloc来进行申请内存,那么底层使用的函数就是emalloc函数,来看下wiki的解释
void *emalloc(size_t size)分配 size 字节的内存。
这个就比较好理解了,传入参数内存再进行一个double拷贝就可以,
那么我们进行一个decode的内存峰值的计算:
峰值内存=(4/3+4/3) *4.89 =13.04
基本与我们测试的结果相差不多,因为精度关系,我们进行四舍五入的计算,测试代码是精准计算,所以会有小数点偏差。
五、总结
那这就可以理解为什么一个为什么在我们一个37M的文件,不能再128M内存进行base64_encode与base64_decode操作,当然这里有一些临时变量没有及时释放内存的情况,但是通过源码分析可以知道,要做一次这样场景来进行文件上传,单纯文件的内存损耗是2.6倍左右,所以为了节省内存,我们不要再用这个方式来进行操作了,很费内存的
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍c# 用Base64实现文件上传,包括了c# 用Base64实现文件上传的使用技巧和注意事项,需要的朋友参考一下 Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,它是一种基于64个可打印字符来表示二进制数据的方法。 使用base64进行文件上传的具体流程是:前台使用js将文件转换为base64格式,后台通过高级编程语言,将base64格式的文件,转换
本文向大家介绍servlet上传文件实现代码详解(四),包括了servlet上传文件实现代码详解(四)的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了servlet上传文件的具体代码,供大家参考,具体内容如下 1.servlet上传文件 servlet上传文件就是将客户端的文件上传到服务器端。 向服务器发送数据时,客户端发送的http请求正文采用“multipart/form
本文向大家介绍php实现上传图片文件代码,包括了php实现上传图片文件代码的使用技巧和注意事项,需要的朋友参考一下 代码很简单,这里我们就不多废话了,直接奉上源码 以上所述就是本文的全部内容了,希望大家能够喜欢。
本文向大家介绍PHP实现文件上传和多文件上传,包括了PHP实现文件上传和多文件上传的使用技巧和注意事项,需要的朋友参考一下 在PHP程序开发中,文件上传是一个使用非常普遍的功能,也是PHP程序员的必备技能之一。值得高兴的是,在PHP中实现文件上传功能要比在Java、C#等语言中简单得多。下面我们结合具体的代码实例来详细介绍如何通过PHP实现文件上传和多文件上传功能。 要使用PHP实现文件上传功能,
本文向大家介绍Webwork 实现文件上传下载代码详解,包括了Webwork 实现文件上传下载代码详解的使用技巧和注意事项,需要的朋友参考一下 本文主要从三个方面给大家介绍webwork文件上传下载知识,包括以下三个方面: 1. 包装 Request 请求 2. 获取文件上传的解析类 3. 项目实战配置和使用 Web上传和下载应该是很普遍的一个需求,无论是小型网站还是大并发访问的交易网站。WebW
本文向大家介绍PHP实现文件上传功能实例代码,包括了PHP实现文件上传功能实例代码的使用技巧和注意事项,需要的朋友参考一下 点击浏览,将所选的文件上传到创建的images文件夹内 代码如下: 1.wenjian.php 2.chuli.php 以上所述是小编给大家介绍的PHP实现文件上传功能实例代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对呐喊教