您的位置: 翼速应用 > 业内知识 > PHP框架 > 正文

深度了解PHP排序稳定性问题

了解PHP排序稳定性问题

最近在工作中碰到一个挺有意思的问题,线上输入是一串排好序的关联数组,经过一系列处理后输出的数组却是乱序,且本地运行无法复现。查看相关代码后,最让人在意的是这一段:

$categories = Arr::sort($categories, function ($node) {
    return $node['default'];
}, true);

作用是按default优先级将元素提到前面,首先确认了下线上的illuminate/support版本和本地一致,查看Arr::sort()源码:

...
$descending ? arsort($results, $options)
            : asort($results, $options);

最终还是调用 php 的asort,线上是 php5 而本地和测试因为最近考虑升级都换成了 php7,最后在 php5 环境下成功复现,确定出是sort问题。

在排序前后相等的元素相对位置不变即说这个算法是稳定的。

对快速排序有一定了解的话可以知道,快速排序是不稳定的所以这段代码在元素default优先级都相同的情况下输出将不会是之前的顺序,但在 php7 环境下测试结果为什么会保留原来的顺序呢。难道关于我之前理解的天底下所有默认排序都是快速排序这一点是错的吗?

好吧,让我们来快速看看 php 源码是如何解决这个问题的。到 github 官方的 php-src 直接搜索asort in this repository,找c文件,找到这个函数定义在 arr.c:814

PHP_FUNCTION(asort)
{
    zval *array;
    zend_long sort_type = PHP_SORT_REGULAR;
    bucket_compare_func_t cmp;

    ZEND_PARSE_PARAMETERS_START(1, 2)
       Z_PARAM_ARRAY_EX(array, 0, 1)
        Z_PARAM_OPTIONAL
        Z_PARAM_LONG(sort_type)
    ZEND_PARSE_PARAMETERS_END();

    // 设置比较函数
    cmp = php_get_data_compare_func(sort_type, 0);
    zend_hash_sort(Z_ARRVAL_P(array), cmp, 0);
    RETURN_TRUE;
}

可以看到最终调用的是zend_hash_sort(),我们继续搜索:

1642922398526677.png

发现这个函数是zend_hash_sort_ex()的套娃,最后找到zend_hash.c:2497

ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, zend_bool renumber)
{
   Bucket *p;
    uint32_t i, j;

    IS_CONSISTENT(ht);
    HT_ASSERT_RC1(ht);

    if (!(ht->nNumOfElements>1) && !(renumber && ht->nNumOfElements>0)) {
        /* Doesn't require sorting */
        return;
    }


    // 这里的hole指数组元素被unset掉产生的洞
    if (HT_IS_WITHOUT_HOLES(ht)) {
        /* Store original order of elements in extra space to allow stable sorting. */
       for (i = 0; i < ht->nNumUsed; i++) {
            Z_EXTRA(ht->arData[i].val) = i;
        }
    } else {
        /* Remove holes and store original order. */
        for (j = 0, i = 0; j < ht->nNumUsed; j++) {
            p = ht->arData + j;
            if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) continue;
            if (i != j) {
                ht->arData[i] = *p;
            }
            Z_EXTRA(ht->arData[i].val) = i;
            i++;
        }
        ht->nNumUsed = i;
    }

 

    sort((void *)ht->arData, ht->nNumUsed, sizeof(Bucket), (compare_func_t) compar,
            (swap_func_t)(renumber? zend_hash_bucket_renum_swap :
                ((HT_FLAGS(ht) & HASH_FLAG_PACKED) ? zend_hash_bucket_packed_swap : zend_hash_bucket_swap)));
    ...

好耶!,官方注释里就有说明是怎么实现排序的稳定性,在排序之前用这个Z_EXTRA保留了原数组元素到下标的映射。


但当我搜索这块代码提交信息时发现了一个问题:稳定排序相关的 rfc 在https://wiki.php.net/rfc/stable_sorting,可以看到是发生在今年五月份且针对 php8.0 的。


?? 那之前的 php7 排序稳定是怎么回事。


马上构造个例子:

$count = 10;
$cc = [];
for ($i=0; $i<$count; $i++) {
    $cc[] = [
        'id' => $i,
        'default' => rand(0, 10),
    ];
}
$cc = Arr::sort($cc, function ($i) {
   return $i['default'];
});
dd($cc);

经过多次在 php7 下的测试发现:当$count比较小的时候,排序才是稳定的,但$count较大情况下的排序又变成不稳定。也就是线上排序不稳定而本地无法复现其实是用例的数组长度太短所致。


看到这里可以确定是快速排序长度阈值优化的问题,最后查了下相关资料。php7 中的sort是基于LLVM中libc++的std::sort实现。当元素数量小于等于16时候有特殊优化,大于16才走快速排序,而 php5 是直接就走快速排序的。

$count = 100;
$cc = [];
for ($i=0; $i<$count; $i++) {
    $cc[] = [
        'id' => $i,
        'default' => rand(0, 10),
    ];
}
usort($cc, function($a, $b){
    if ($a['default'] == $b['default']) return 0;
    return ($a['default'] < $b['default']) ? 1 : -1;
});
print_r($cc);

最后在 php8 环境下试了试,排序绝对稳定

以上就是对深度了解PHP排序稳定性问题的详细内容,更多请关注翼速应用PHP框架分类专区查看更多其它相关文章!


我来说两句

0 条评论

推荐阅读

  • 响应式布局CSS媒体查询设备像素比介绍

    构建响应式网站布局最常见的是流体网格,灵活调整大小的站点布局技术,确保用户在使用的幕上获得完整的体验。响应式设计如何展示富媒体图像,可以通过以下几种方法。

    admin
  • 提升网站的性能快速加载的实用技巧

    网站速度很重要,快速加载的网站会带来更好的用户体验、更高的转化率、更多的参与度,而且在搜索引擎排名中也扮演重要角色,做SEO,网站硬件是起跑线,如果输在了起跑线,又怎么跟同行竞争。有许多方法可提升网站的性能,有一些技巧可以避免踩坑。

    admin
  • 织梦CMS TAG页找不到标签和实现彩色标签解决方法

    织梦cms是我们常见的网站程序系统的一款,在TAG标签中常常遇到的问题也很多。当我们点击 tags.php 页的某个标签的时候,有时会提示:“系统无此标签,可 能已经移除!” 但是我们检查程序后台,以及前台显示页面。这个标签确实存在,如果解决这个问题那?

    admin
  • HTML关于fieldset标签主要的作用

    在前端开发html页面中常用的标签很多,今天为大家带来的是关于HTML中fieldset标签主要的作用说明,根据技术分析HTML

    admin

精选专题