PHP 中的对象比较和数组排序
Object comparison and array sorting in PHP
我在 PHP 中遇到对象比较问题。看起来简单的代码实际上运行方式 太慢 不符合我的喜好,而且由于我的语言不是那么先进,我想得到一些关于以下代码的反馈和建议:
class TestTokenGroup {
private $tokens;
...
public static function create($tokens) {
$instance = new static();
$instance->tokens = $tokens;
...
return $instance;
}
public function getTokens() {
return $this->tokens;
}
public static function compare($tokenGroup1, $tokenGroup2) {
$i = 0;
$minLength = min(array(count($tokenGroup1->getTokens()), count($tokenGroup2->getTokens())));
$equalLengths = (count($tokenGroup1->getTokens()) == count($tokenGroup2->getTokens()));
$comparison = strcmp($tokenGroup1->getTokens()[$i], $tokenGroup2->getTokens()[$i]);
while ($comparison == 0) {
$i++;
if (($i == $minLength) && ($equalLengths == true)) {
return 0;
}
$comparison = strcmp($tokenGroup1->getTokens()[$i], $tokenGroup2->getTokens()[$i]);
}
$result = $comparison;
if ($result < 0)
return -1;
elseif ($result > 0)
return 1;
else
return 0;
}
...
}
在上面的代码中 $tokens
只是一个简单的字符串数组。
通过 usort()
使用上面的方法得到一个由大约 40k 个对象组成的 TestTokenGroup
数组需要大约 2 秒。
有什么合理的方法可以加快速度吗?这里的瓶颈在哪里?
编辑:添加了我最初忘记包含的 getTokens() 方法。
你知道对象是"pass by reference",数组是"pass by value"吗?
如果getTokens()
returns $this->tokens
,每次调用该方法时都会复制数组。
尝试通过 $tokenGroup1->tokens
直接访问 $tokens
。您也可以使用引用 (&
),尽管返回引用在所有 PHP 版本中都不起作用。
或者,只复制一份:
$tokens1 = $tokenGroup1->getTokens();
$tokens2 = $tokenGroup2->getTokens();
即使每个令牌组比较小,也至少会保存40000 * ( 6 + $average_token_group_length * 2)
个数组副本。
更新
我使用以下方法对 OP 的代码进行了基准测试(删除了 ...
行):
function gentokens() {
$ret = [];
for ( $i=0; $i< 3; $i++)
{
$str = "";
for ( $x = rand(0,3); $x < 10; $x ++ )
$str .= chr( rand(0,25) + ord('a') );
$ret[] = $str;
}
return $ret;
}
$start = microtime(true);
$array = []; // this will hold the TestTokenGroup instances
$dummy = ""; // this will hold the tokens, space-separated and newline-separated
$dummy2= []; // this will hold the space-concatenated strings
for ( $i=0; $i < 40000; $i++)
{
$array[] = TestTokenGroup::create( $t = gentokens() );
$dummy .= implode(' ', $t ) . "\n";
$dummy2[] = implode(' ', $t );
}
// write a test file to benchmark GNU sort:
file_put_contents("sort-data.txt", $dummy);
$inited = microtime(true);
printf("init: %f s\n", ($inited-$start));
usort( $array, [ 'TestTokenGroup', 'compare'] );
$sorted = microtime(true);
printf("sort: %f s\n", ($sorted-$inited));
usort( $dummy2, 'strcmp' );
$sorted2 = microtime(true);
printf("sort: %f s\n", ($sorted2-$sorted));
结果如下:
init: 0.359329 s // for generating 40000 * 3 random strings and setup
sort: 1.012096 s // for the TestTokenGroup::compare
sort: 0.120583 s // for the 'strcmp' compare
并且,运行 time sort sort-data.txt > /dev/null
产量
.052 u (user-time, in seconds).
优化1:删除数组副本
用 ->tokens
替换 ->getTokens()
得到(我只会列出 TestTokenGroup::compare
结果):
sort: 0.832794 s
优化2:删除min
中多余的array()
将 $minlength
行更改为:
$minLength = min(count($tokenGroup1->tokens), count($tokenGroup2->tokens));
给予
sort: 0.779134 s
优化3:每个tokenGroup
只调用一次count
$count1 = count($tokenGroup1->tokens);
$count2 = count($tokenGroup2->tokens);
$minLength = min($count1, $count2);
$equalLengths = ($count1 == $count2);
给予
sort: 0.679649 s
替代方法
到目前为止最快的排序是 strcmp( $stringarray, 'strcmp' )
:0.12s - 仍然是 GNU 排序的两倍,但后者只做一件事,而且做得很好。
因此,为了有效地对 TokenGroups 进行排序,我们需要构造由简单字符串组成的排序键。我们可以使用 [=38=]
作为标记的分隔符,我们不必担心它们的长度相等,因为一旦一个字符不同,比较就会中止。
实现如下:
$arr2 = [];
foreach ( $array as $o )
$arr2[ implode("[=19=]", $o->getTokens() ) ] = $o;
$init2 = microtime(true);
printf("init2: %f s\n", ($init2-$sorted2));
uksort( $arr2, 'strcmp' );
$sorted3 = microtime(true);
printf("sort: %f s\n", ($sorted3-$init2));
结果如下:
init2: 0.125939 s
sort: 0.104717 s
我在 PHP 中遇到对象比较问题。看起来简单的代码实际上运行方式 太慢 不符合我的喜好,而且由于我的语言不是那么先进,我想得到一些关于以下代码的反馈和建议:
class TestTokenGroup {
private $tokens;
...
public static function create($tokens) {
$instance = new static();
$instance->tokens = $tokens;
...
return $instance;
}
public function getTokens() {
return $this->tokens;
}
public static function compare($tokenGroup1, $tokenGroup2) {
$i = 0;
$minLength = min(array(count($tokenGroup1->getTokens()), count($tokenGroup2->getTokens())));
$equalLengths = (count($tokenGroup1->getTokens()) == count($tokenGroup2->getTokens()));
$comparison = strcmp($tokenGroup1->getTokens()[$i], $tokenGroup2->getTokens()[$i]);
while ($comparison == 0) {
$i++;
if (($i == $minLength) && ($equalLengths == true)) {
return 0;
}
$comparison = strcmp($tokenGroup1->getTokens()[$i], $tokenGroup2->getTokens()[$i]);
}
$result = $comparison;
if ($result < 0)
return -1;
elseif ($result > 0)
return 1;
else
return 0;
}
...
}
在上面的代码中 $tokens
只是一个简单的字符串数组。
通过 usort()
使用上面的方法得到一个由大约 40k 个对象组成的 TestTokenGroup
数组需要大约 2 秒。
有什么合理的方法可以加快速度吗?这里的瓶颈在哪里?
编辑:添加了我最初忘记包含的 getTokens() 方法。
你知道对象是"pass by reference",数组是"pass by value"吗?
如果getTokens()
returns $this->tokens
,每次调用该方法时都会复制数组。
尝试通过 $tokenGroup1->tokens
直接访问 $tokens
。您也可以使用引用 (&
),尽管返回引用在所有 PHP 版本中都不起作用。
或者,只复制一份:
$tokens1 = $tokenGroup1->getTokens();
$tokens2 = $tokenGroup2->getTokens();
即使每个令牌组比较小,也至少会保存40000 * ( 6 + $average_token_group_length * 2)
个数组副本。
更新
我使用以下方法对 OP 的代码进行了基准测试(删除了 ...
行):
function gentokens() {
$ret = [];
for ( $i=0; $i< 3; $i++)
{
$str = "";
for ( $x = rand(0,3); $x < 10; $x ++ )
$str .= chr( rand(0,25) + ord('a') );
$ret[] = $str;
}
return $ret;
}
$start = microtime(true);
$array = []; // this will hold the TestTokenGroup instances
$dummy = ""; // this will hold the tokens, space-separated and newline-separated
$dummy2= []; // this will hold the space-concatenated strings
for ( $i=0; $i < 40000; $i++)
{
$array[] = TestTokenGroup::create( $t = gentokens() );
$dummy .= implode(' ', $t ) . "\n";
$dummy2[] = implode(' ', $t );
}
// write a test file to benchmark GNU sort:
file_put_contents("sort-data.txt", $dummy);
$inited = microtime(true);
printf("init: %f s\n", ($inited-$start));
usort( $array, [ 'TestTokenGroup', 'compare'] );
$sorted = microtime(true);
printf("sort: %f s\n", ($sorted-$inited));
usort( $dummy2, 'strcmp' );
$sorted2 = microtime(true);
printf("sort: %f s\n", ($sorted2-$sorted));
结果如下:
init: 0.359329 s // for generating 40000 * 3 random strings and setup
sort: 1.012096 s // for the TestTokenGroup::compare
sort: 0.120583 s // for the 'strcmp' compare
并且,运行 time sort sort-data.txt > /dev/null
产量
.052 u (user-time, in seconds).
优化1:删除数组副本
用 ->tokens
替换 ->getTokens()
得到(我只会列出 TestTokenGroup::compare
结果):
sort: 0.832794 s
优化2:删除min
array()
将 $minlength
行更改为:
$minLength = min(count($tokenGroup1->tokens), count($tokenGroup2->tokens));
给予
sort: 0.779134 s
优化3:每个tokenGroup
count
$count1 = count($tokenGroup1->tokens);
$count2 = count($tokenGroup2->tokens);
$minLength = min($count1, $count2);
$equalLengths = ($count1 == $count2);
给予
sort: 0.679649 s
替代方法
到目前为止最快的排序是 strcmp( $stringarray, 'strcmp' )
:0.12s - 仍然是 GNU 排序的两倍,但后者只做一件事,而且做得很好。
因此,为了有效地对 TokenGroups 进行排序,我们需要构造由简单字符串组成的排序键。我们可以使用 [=38=]
作为标记的分隔符,我们不必担心它们的长度相等,因为一旦一个字符不同,比较就会中止。
实现如下:
$arr2 = [];
foreach ( $array as $o )
$arr2[ implode("[=19=]", $o->getTokens() ) ] = $o;
$init2 = microtime(true);
printf("init2: %f s\n", ($init2-$sorted2));
uksort( $arr2, 'strcmp' );
$sorted3 = microtime(true);
printf("sort: %f s\n", ($sorted3-$init2));
结果如下:
init2: 0.125939 s
sort: 0.104717 s