【问题标题】:Is PHP usort really so slow or am I doing something wrong?PHP usort 真的这么慢还是我做错了什么?
【发布时间】:2015-12-27 15:38:30
【问题描述】:

我有一个代表 SMS 的对象的数组(嗯,一个 PHP 数组...它不是真正的数组。但你明白了。)。这些对象中的一个字段是 DateTime 类型,我想按该字段对数组进行排序。 我无法对 DB 中的数据进行排序,我从无法更改的 Web 服务接收数据,所以请不要这样做。我使用以下 sn-p 代码对数组进行排序:

usort($smsMessages, function ($a, $b) { 
    if ($a->SendTime == $b->SendTime) {
        return 0;
    }

    return ($a->SendTime < $b->SendTime) ? -1 : 1;
});

这可行,但需要 160 秒 对 30.000 个元素进行排序。

现在,我知道 php 很慢,但这很荒谬。我写这个的方式有问题吗? usort 是否已知速度慢/损坏/错误?我应该使用其他方法吗?自己滚?

【问题讨论】:

  • 说真的,你抱怨排序 30K 元素?
  • 在 i7(第 4 代)上(您的代码)大约需要 1.3 秒来处理 30k 个元素。
  • @u_mulder - 我抱怨排序糟糕的 30k 元素需要 160 秒的时间。
  • @u_mulder:应该不会花三分钟。
  • @JeremyJackson - 是的,我可以对其进行排序

标签: php arrays sorting


【解决方案1】:

我有同样的问题。我们需要对 2-10 百万个数组进行排序。每个数组包含大约 30 个字段(字符串、整数和 NULL)。第一个字段是我们用于排序的唯一整数。

我们使用 PHP 7.1

在 AWS EC2 r4.large 上对 2,028,830 个项目进行排序需要 4710 秒(= 78.5 分钟)。

我们的代码如下所示:

usort($this->rows, function ($item1, $item2) {
        return $item1[0] <=> $item2[0];
});

然后我发现用$rows 替换$this-&gt;rows 可以让它快4 倍:

usort($rows, function ($item1, $item2) {
        return $item1[0] <=> $item2[0];
});

它将执行时间从 4710 秒减少到 1195 秒。

另一种方法是使用Min Heap 代替$this-&gt;rows,而不是普通的PHP 数组[]。它导致了大致相同的性能改进。在这种情况下,您根本不需要 usort。

底线: 1. 但是,是的,即使在进行了上述更改之后,它也确实需要大量的时间。 2. 对于已经排序的数组,usort 比 MinHeap 快得多。

【讨论】:

    【解决方案2】:

    您可以尝试加速上面的代码,看看排序是否真的是瓶颈,您可以尝试通过使用全局函数并缩短代码来加速它(免责声明:大规模微优化,可能是这样不是你的问题所在!)像这样:

    function sort_function($a, $b){
     $a = $a->SendTime;
     $b = $b->SendTime;
     if ($a == $b) return 0;
     return ($a < $b) ? -1 : 1;
    }
    
    usort($smsMessages,'sort_function');
    

    假设大多数 SendTimes 不相等,这实际上应该加快速度。

    请理解以上只是一个非常轻微的加速。如果你真的看到事情变得像 140s => 你可以在这里责怪 usort。不过,上述建议对您的价值很可能在于了解事情的优劣部分在我看来不是您的问题。

    在以下更多输入后添加:

    在可能了解到这完全是因为内存不足之后(您发布的使用数字是关于整个系统的,如果没有更多关于这些对象的知识,我无法推断出这 256MB 中实际使用了多少 :)) ,这段代码在运行时对你来说如何比较?

    $dates = array();
    foreach ($smsMessages as $key => $obj) {
        $dates[$key] = $obj->SendTime;
    }
    
    asort($dates);
    $dates = array_keys($dates);
    $sorted = array();
    foreach ($dates as $key) {
        $sorted[] = &$smsMessages[$key];
    }
    

    这应该需要更少的内存,因为它没有在巨大的数组上使用隐式 foreach 循环,而只是在数组键上使用。

    【讨论】:

    • 您的代码需要大约 0.5 秒。在 i7(第 4 代)上,30k 个元素。
    • 至少我显然知道如何进行微优化 :D 让我们看看 160 后会发生什么 :P
    • @Axalix 为您的对象添加更多属性,绝对 OP 不会仅对具有单个属性的对象进行排序。
    • 是的,它下降到 152 秒 :) @u_mulder - 是的,对象有额外的 sms_text、发件人和收件人字段。这些都不是用于排序的,它们应该对排序速度没有影响。 30k 个元素的内存使用量也可以忽略不计。
    • 嗯哇,这真是太疯狂了,说实话,8s 的差异是最不期望的结果......如果你只运行这段代码会怎样: $arr = array(); foreach($smsMessages as $msg) { $arr[] = $msg->Sendtime;这需要多长时间?
    【解决方案3】:

    试试这个:

    首先,将“true”添加到json_decode 作为第二个参数,这样您将得到一个关联数组而不是对象数组。 (我也建议尝试这个来加速 JSON:https://github.com/RustJason/php-rapidjson - 虽然它需要 PHP7)

    然后:

    $sentTime = [];
    foreach ($smsMessages as $key => $element) {
        $sentTime[$key] = strtotime($element['sent']);
    }
    array_multisort($sentTime, SORT_DESC, $smsMessages);
    

    (我的比赛为 0.19 秒)

    您可以稍后在真正需要它们时使用(object)$smsMessage 或使用您自己/自定义的方法将一些$smsMessages 转换为对象。

    【讨论】:

    • 我正在按时间戳对日志条目数组(范围从 20 到 150000 个条目)进行排序,这种方法(首先从每个条目中提取排序键,然后使用 array_multisort)是 much比 usort() 快!谢谢!
    猜你喜欢
    • 2017-08-23
    • 1970-01-01
    • 1970-01-01
    • 2011-05-02
    • 1970-01-01
    • 2010-12-14
    • 1970-01-01
    • 1970-01-01
    • 2017-05-21
    相关资源
    最近更新 更多