记忆:连接参数或执行md5哈希?

时间:2016-03-17 15:15:52

标签: php memoization

我正在为几个函数添加memoization。这些函数需要2-3个字符串参数(对象名称),可选的int参数(记录ID)和布尔参数(包括已删除的记录)。保证每个参数组合都能产生一个独特的结果(因此值得缓存)。

我想知道连接给定参数($param1 . $param2 . $param3等)并将其用作数组键,或者使用相同的连接字符串并使用md5哈希作为键是否更快。在99%的情况下,连接参数字符串的长度在20-32个字符之间(平均大约27个),而md5哈希值总是32个字符。
编辑 :md5哈希只有16个字节,而不是32.谢谢Mjh。

我倾向于第一个选项,因为它:

  • 节省了执行md5哈希的费用
  • 它通常会节省几个字节的内存(27个平均值与32个哈希值)(Mjh指出这不是真的:md5只有16个字节)
  • 因为md5哈希只是另一个字符串,所以比较短字符串
  • 通常会更快

我怀疑的唯一原因是绝大多数的memoization函数似乎都使用(md5)哈希,所以我想知道我是否遗漏了一些东西。

提前致谢。

P.S。我忘了提及:我将个别参数与#字符分开,这在任何参数中都不会自然出现。

P.P.S。到目前为止,ankhzet的评论似乎是最佳解决方案,因为我的字符串在开始时几乎是唯一的:crc32($paramString)。内存占用少,校验和计算功能非常快。

测试crc32()性能

下面是一个测试脚本,它填充4个阵列,每个阵列有100万key => value对。所有4个阵列中的values都是相同的。 keys也是相同的,除了对于前2个数组,连接的字符串键首先运行crc32()

$test1Array = [];
$start1 = microtime(true);
for ($i = 0; $i < 1000000; $i++)
{
    $test1Array[crc32("pagemanagement" . "#" . "staticblocktype" . "#" . $i . "#" . 1)] = "test " . $i;
}
$end1 = microtime(true);

$test2Array = [];
$start2 = microtime(true);
for ($j = 0; $j < 1000000; $j++)
{
    $test2Array[crc32("pagemanagement" . "#" . "staticblocktype" . "#" . $i . "#" . 1)] = "test " . $j;
}
$end2 = microtime(true);

$test3Array = [];
$start3 = microtime(true);
for ($x = 0; $x < 1000000; $x++)
{
    $test3Array["pagemanagement" . "#" . "staticblocktype" . "#" . $i . "#" . 1] = "test " . $x;
}
$end3 = microtime(true);

$test4Array = [];
$start4 = microtime(true);
for ($y = 0; $y < 1000000; $y++)
{
    $test4Array["pagemanagement" . "#" . "staticblocktype" . "#" . $i . "#" . 1] = "test " . $y;
}
$end4 = microtime(true);

3次试运行的结果:
测试1:3.9902291297913
测试2:3.6312079429626
测试3:0.91605305671692
测试4:0.91405177116394

测试1:3.9842278957367
测试2:3.6172070503235
测试3:0.91405200958252
测试4:0.918053150177

测试1:3.9842278957367
测试2:3.6282079219818
测试3:0.91205215454102
测试4:0.91605186462402

如果我取所有“测试2”和“测试4”值的平均值(因为“测试1”似乎有初始化开销),我留下3.6255409717560用于“测试2”和0.9160522619883用于“测试4” 。这是2.7094887097677的差异,并且(2.7094887097677 / 1000000)= 0.0000027094887或每个函数调用2.72微秒。

不幸的是,我目前无法轻松计算内存使用情况,但保证存储4字节crc32()值的内存要比平均27字符长度字符串少得多。假设最佳情况为1字节字符,即每个缓存结果的差异为23个字节。

为了完整性,我还使用md5()进行了快速测试:
测试1:4.2855787277221
测试2:3.8108838399251
实际上,md5()crc32()之间的性能差异很小,我感到很惊讶。当然,crc32()仍然具有仅使用4个字节到md5() 16的优势。

结论:由于我的函数的主要开销是在重复的数据库调用中,并且由于这些函数每个请求大约需要50-200次调用,我个人认为~135增加-540微秒的计算时间值得保存~1150-4600字节的内存。

如果有人不同意我的测试和/或结论,我很想知道。

3 个答案:

答案 0 :(得分:2)

这是我使用PHP7在AMD 2x2.3 GHz计算机上进行md5-crc32-sha1-native哈希的天真性能测试:

function probe($label, $times, $callback) {
    $mem = memory_get_usage();
    $start = microtime(true);
    $array = $callback($times);
    $time = microtime(true) - $start;
    $mem = sprintf('%.3f', (memory_get_usage() - $mem) / 1024 / 1024);
    return "$label:  $time s, $mem MB";
}

$times = 1000000;

$run1 = probe('String key', $times, function ($times) {
    $a = [];
    while ($times-- > 0) {
        $a["pagemanagement" . "#" . "staticblocktype" . "#" . $times . "#" . 1] = "test " . $times;
    }
    return $a;
});

$run2 = probe('CRC32 key', $times, function ($times) {
    $a = [];
    while ($times-- > 0) {
        $a[crc32("pagemanagement" . "#" . "staticblocktype" . "#" . $times . "#" . 1)] = "test " . $times;
    }
    return $a;
});

$run3 = probe('MD5 key', $times, function ($times) {
    $a = [];
    while ($times-- > 0) {
        $a[md5("pagemanagement" . "#" . "staticblocktype" . "#" . $times . "#" . 1)] = "test " . $times;
    }
    return $a;
});

$run4 = probe('SHA1 key', $times, function ($times) {
    $a = [];
    while ($times-- > 0) {
        $a[sha1("pagemanagement" . "#" . "staticblocktype" . "#" . $times . "#" . 1)] = "test " . $times;
    }
    return $a;
});

echo join("<br/>\n", [
    $run1,
    $run2,
    $run3,
    $run4,
    ]);
  

字符串键:1.2421879768372 s,111.923 MB
  CRC32密钥:1.3447260856628 s,58.517 MB
  MD5密钥:2.1748039722443 s,111.923 MB
  SHA1密钥:2.2480459213257 s,119.552 MB

看起来MD5比crc32稍微宽松一点,而crc32显然有更少的内存开销。

对于PHP5.5 + -PHP7&amp; hhvm版本。

编辑:添加了粗略的内存分配测试(演示链接也已更新)。看起来crc32在建议的测试集上的内存大约减少了1.5-2倍。

修改:添加了sha1测试。看起来更慢,更讨厌md5。

注意:改组测试命令没有任何改变,因此,没有热身/内存分配会对结果产生重大影响。

答案 1 :(得分:1)

将其存储在数组中时:

$cache[$paramString] = $value;  // or
$cache[crc32($paramString)] = $value;

PHP将从密钥创建一个哈希值,它将它存储为unsigned long。它还将存储实际的$ paramString以及所需的其他数据。所以,我没有看到你真正从做crc32()或md5()获得任何东西,特别是因为$ paramString通常不会那么大。

此页面包含许多详细信息:https://nikic.github.io/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html

答案 2 :(得分:-1)

连接值只有在对象的toString方法不隐藏任何影响函数行为的特征时才会起作用,并且函数不会实例化依赖于任何隐藏特征的任何东西。但由于我们讨论的是面向对象,我们无法确定连接字符串的大小是否可预测。

因此仅仅因为这个原因你应该使用哈希。

您可以考虑使用sha1(),尽管它比md5()实际运行速度更快(至少在我最后一次检查时)。

但这闻起来像是对我过早优化。