php 中日期的内存高效表示

Memory-efficient representation of dates in php

我正在计算大量日期对(开始和结束)并且 运行 遇到了内存问题。 我当前的表示是基于字符串的,但仍然效率低下:

目前我将这两个日期序列化为 manual-YYYY-MM-DD-YYYY-MM-DD,即 28 个字节。 我可以通过切换到 YYYYMMDDYYYYMMDD.

将其降低到 16

但是,如果我现在要更改格式,我想知道是否有更有效的解决方案。理论上没有太多的日期,所以一个 4 字节的 int 应该足够了,让我减少到 8 字节。

我最初决定使用字符串表示,因为它 (a) 人类可读且 (b) 易于重复数据删除(想想这样的句点数组 in_array

给出数字的概念:我正在设置一个周期数组,所以一个包含 813.000 个数组的列表总共包含 6500 万个这样的项目(很多很多重复项,大部分时间是月初-结束于 2021-05-01-2021-05-31)。所以我也很感兴趣,如果一些查找字典解决方案可能有帮助...

为了节省内存,您可以将日期存储为 int 表示自参考日期以来的天数,例如最早的日期,如果您提前知道那是什么,或者可能是日期否则肯定早于整个数据集。

一个int占据了zend_value的大小,在PHP中占8个字节>=7,因为它直接作为zend_value本身的一部分存储(见zend_value, which is part of _zval_struct, aka zval),因此完全没有内存开销。

这是一个值可能占用的最小内存空间,因为除 intzend_value, a zend_long) or double (dval in zend_value, a double) is stored as a pointer to a value allocated separately (*str, *arr, *obj, etc... in zend_value 中的 lval)以外的任何其他内容都会占用指针的大小,这等于 zend_value 的大小及其指向的结构的大小。

比如一个字符串(zend_string) takes: 8 bytes (the size of zend_refcounted_h for gc) + 8 bytes (the size of zend_ulong for h) + 8字节(size_t的大小 for len) + 1字节(len的大小) =38=] for val) = 25 字节开销 + 8 字节(zend_value 的大小)= 空字符串 33 字节 + 每个字符多一个字节(或多字节字符串多字节) ).

你可以在一个空字符串占用的内存中容纳 4 ints。

如果您想准确计算 PHP 值占用多少内存,请记住您需要考虑 zval containing the zend_value 添加的开销,这可能因 PHP版本。

由于您有日期范围,您还可以考虑将范围表示为 int(类似于 $first_day * 1000000 + $last_day),从而节省了用两个 zval 来表示的开销两个 ints.


查找 table 应该也有帮助,尤其是当您有很多相似的日期时。

您可以设置一个日期数组和一个索引数组。遇到日期,看index[date]是否存在,如果存在,则该值为日期数组中的索引,如果不存在,则将其插入到日期数组中,并存储索引。类似于:

if (!isset($index[$date])) {
    $dates []= $date;
    $index[$date] = count($dates) - 1;
}
$date_index = $index[$date];

现在您可以存储 $date_indexes 而不是完整的日期,并且可以引用 $index 以在需要时检索日期。


日期不必在内存中是人类可读的,只要在显示它们时即可。

因此,您可以编写一个显示函数来执行所有必要的操作,以人类可读的形式显示数据:使用 $date_index$dates 获取日期,然后将其从其内部表示形式转换为一个可读的字符串。


我也鼓励您考虑 。这听起来像是一项很长的 运行 任务,而不是用户需要将其视为对请求的响应,因此也许您可以以较小的批次处理数据,而不是一次处理所有数据。

如果输入没有改变,因此计算结果也没有改变,那么还要考虑缓存结果,这样您就不必再次执行所有这些操作。在这种情况下,您还可以考虑在大量数据条目可用时立即进行计算并缓存结果,这应该会使用更少的处理能力并更快地获得结果。