PHP asort时间复杂度源码探索(7.0)

首先查看asort函数先找到其实现文件,PHP的源码中所有的标准库函数都定义在ext/standard目录中,asort其功能为array所使用,所以我们在array.c中果然找到了此方法的实现

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

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &array, &sort_type) == FAILURE) {
        RETURN_FALSE;
    }

    cmp = php_get_data_compare_func(sort_type, 0);

    if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 0) == FAILURE) {
        RETURN_FALSE;
    }
    RETURN_TRUE;
}
  • 首先定义了zval实现的array指针,并定义了sort_type为PHP_SORT_REGULAR,这个变量在后面比较类型判断的时候使用;
  • 又调用了zend_parse_parameters,这个函数就不在这里解析了,这个函数的实现结果是获取调用函数的传入参数,第一个参数是array,也就是我们需要排序的数组地址,我们可以看到其实有个传入参数sort_type,这里需要注意下这个传入参数.
  • 接下来就是php_get_data_compare_func,这个函数是用作根据传入的sort_type来获取要使用的比较方法,我们来分析下这个方法
static compare_func_t php_get_data_compare_func(zend_long sort_type, int reverse) /* {{{ */
{
    switch (sort_type & ~PHP_SORT_FLAG_CASE) {
        case PHP_SORT_NUMERIC:
            if (reverse) {
                return php_array_reverse_data_compare_numeric;
            } else {
                return php_array_data_compare_numeric;
            }
            break;

        case PHP_SORT_STRING:
            if (sort_type & PHP_SORT_FLAG_CASE) {
                if (reverse) {
                    return php_array_reverse_data_compare_string_case;
                } else {
                    return php_array_data_compare_string_case;
                }
            } else {
                if (reverse) {
                    return php_array_reverse_data_compare_string;
                } else {
                    return php_array_data_compare_string;
                }
            }
            break;

        case PHP_SORT_NATURAL:
            if (sort_type & PHP_SORT_FLAG_CASE) {
                if (reverse) {
                    return php_array_reverse_natural_case_compare;
                } else {
                    return php_array_natural_case_compare;
                }
            } else {
                if (reverse) {
                    return php_array_reverse_natural_compare;
                } else {
                    return php_array_natural_compare;
                }
            }
            break;

#if HAVE_STRCOLL
        case PHP_SORT_LOCALE_STRING:
            if (reverse) {
                return php_array_reverse_data_compare_string_locale;
            } else {
                return php_array_data_compare_string_locale;
            }
            break;
#endif

        case PHP_SORT_REGULAR:
        default:
            if (reverse) {
                return php_array_reverse_data_compare;
            } else {
                return php_array_data_compare;
            }
            break;
    }
    return NULL;
}

此篇文章我们只比较常规情况下PHP_SORT_REGULAR类型下的复杂度

  • 如上函数实现,我们可以看到我们asort情况也就是升序下调用的是php_array_data_compare,如果是arsort降序下是php_array_reverse_data_compare。
    cmp = php_get_data_compare_func(sort_type, 0);
    if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 0) == FAILURE) {
        RETURN_FALSE;
    }

此时已经返回了我们要使用的比较函数,接下来就是调用zend_hash_sort进行最终的处理了
zend_hash_sort定义肯定就要去zend目录进行查看了,此函数定义在zend_hash.h文件中

#define zend_hash_sort(ht, compare_func, renumber) \
    zend_hash_sort_ex(ht, zend_sort, compare_func, renumber)

可以看到h文件中定义了zend_hash_sort为zend_hash_sort_ex的实现,所以我们跟踪zend_hash_sort_ex,查看zend_hash.c文件,找到zend_hash_sort_ex的实现

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

    IS_CONSISTENT(ht);
    HT_ASSERT(GC_REFCOUNT(ht) == 1);

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

    if (ht->nNumUsed == ht->nNumOfElements) {
        i = ht->nNumUsed;
    } else {
        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;
            }
            i++;
        }
    }

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

    HANDLE_BLOCK_INTERRUPTIONS();
    ht->nNumUsed = i;
    ht->nInternalPointer = 0;

    if (renumber) {
        for (j = 0; j < i; j++) {
            p = ht->arData + j;
            p->h = j;
            if (p->key) {
                zend_string_release(p->key);
                p->key = NULL;
            }
        }

        ht->nNextFreeElement = i;
    }
    if (ht->u.flags & HASH_FLAG_PACKED) {
        if (!renumber) {
            zend_hash_packed_to_hash(ht);
        }
    } else {
        if (renumber) {
            void *new_data, *old_data = HT_GET_DATA_ADDR(ht);
            Bucket *old_buckets = ht->arData;

            new_data = pemalloc(HT_SIZE_EX(ht->nTableSize, HT_MIN_MASK), (ht->u.flags & HASH_FLAG_PERSISTENT));
            ht->u.flags |= HASH_FLAG_PACKED | HASH_FLAG_STATIC_KEYS;
            ht->nTableMask = HT_MIN_MASK;
            HT_SET_DATA_ADDR(ht, new_data);
            memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed);
            pefree(old_data, ht->u.flags & HASH_FLAG_PERSISTENT & HASH_FLAG_PERSISTENT);
            HT_HASH_RESET_PACKED(ht);
        } else {
            zend_hash_rehash(ht);
        }
    }

    HANDLE_UNBLOCK_INTERRUPTIONS();

    return SUCCESS;
}

我们只分析此实现中最重要的一句

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

我们可以看到这里使用sort去进行排序,sort为此函数体中第二个传入的参数,sort就是在h文件中传入的zend_sort,我们进入zend_sort(zend_sort.c定义)进行分析

ZEND_API void zend_sort(void *base, size_t nmemb, size_t siz, compare_func_t cmp, swap_func_t swp)
{
    while (1) {
        if (nmemb <= 16) {
            zend_insert_sort(base, nmemb, siz, cmp, swp);
            return;
        } else {
            char *i, *j;
            char *start = (char *)base;
            char *end = start + (nmemb * siz);
            size_t offset = (nmemb >> Z_L(1));
            char *pivot = start + (offset * siz);

            if ((nmemb >> Z_L(10))) {
                size_t delta = (offset >> Z_L(1)) * siz;
                zend_sort_5(start, start + delta, pivot, pivot + delta, end - siz, cmp, swp);
            } else {
                zend_sort_3(start, pivot, end - siz, cmp, swp);
            }
            swp(start + siz, pivot);
            pivot = start + siz;
            i = pivot + siz;
            j = end - siz;
            while (1) {
                while (cmp(pivot, i) > 0) {
                    i += siz;
                    if (UNEXPECTED(i == j)) {
                        goto done;
                    }
                }
                j -= siz;
                if (UNEXPECTED(j == i)) {
                    goto done;
                }
                while (cmp(j, pivot) > 0) {
                    j -= siz;
                    if (UNEXPECTED(j == i)) {
                        goto done;
                    }
                }
                swp(i, j);
                i += siz;
                if (UNEXPECTED(i == j)) {
                    goto done;
                }
            }
done:
            swp(pivot, i - siz);
            if ((i - siz) - start < end - i) {
                zend_sort(start, (i - start)/siz - 1, siz, cmp, swp);
                base = i;
                nmemb = (end - i)/siz;
            } else {
                zend_sort(i, (end - i)/siz, siz, cmp, swp);
                nmemb = (i - start)/siz - 1;
            }
        }
    }
}
  • 4-7行进行了判断, 如果元素总数 <= 16 ,则进行插入排序,这是很正常的,因为插入排序在最好的情况下时O(n) 级的算法,而数据量小的情况下,数据有序的可能性就越高,也就符合了最好的情况
  • 后面就是 如果元素总数 > 16, 则开始了另外一种排序,首先确定了头元素和尾元素,并让元素总数右移一位作为基准, 这里涉及一个函数 Z_L ,后面做补充解释
  • 继续判断 nmemb >> Z_L(10) 如果 元素总数 右移 10位,依然大于 0的话,选取的偏移数 再向右偏移 1位 作为 delta, 然后 zend_sort_5(start, start + delta, pivot, pivot + delta, end - siz, cmp, swp); 进行交换,否则 执行 zend_sort_3(start, pivot, end - siz, cmp, swp); 执行排序
  • 下面就是标准的快速排序的操作思路了,只是一般我们会使用递归来处理,但是递归会消耗空间,所以 PHP源码里面选择了非递归的方式
    最后看一下zend_sort_3的实现
static inline void zend_sort_3(void *a, void *b, void *c, compare_func_t cmp, swap_func_t swp) /* {{{ */ {
    if (!(cmp(a, b) > 0)) {
        if (!(cmp(b, c) > 0)) {
            return;
        }
        swp(b, c);
        if (cmp(a, b) > 0) {
            swp(a, b);
        }
        return;
    }
    if (!(cmp(c, b) > 0)) {
        swp(a, c);
        return;
    }
    swp(a, b);
    if (cmp(b, c) > 0) {
        swp(b, c);
    }
}

总结

php的asort函数在元素数 较小(16个及以下)的时候,使用插入排序,否则使用非递归的快速排序来进行排序

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容