讨论

在上一篇博客『如何使用PHP最高效率的将一个正整数扩大一千倍?』的讨论区,有人提出位移符应该才是运算最快的方案。

以前总是看到位移符<<这样的符号。因为它总是能轻易把一个数字变成我不认识的模样,所以我也没有深入了解过。

在看到讨论区留言后才意识到:自己的格局太小了。

简单读了读位移符的文档和实现原理,我觉得这种方案还是值得一试的。按照之前两篇博客的心得,我首先抛出我的猜测:

现在进行一个简单的验证。

  • 方案一:$integer * 1000
  • 方案二:($integer << 10) - ($integer * 24)
  • 方案三:($integer * 1024) - ($integer * 24)

PHP

代码部分

这次吸取讨论区提到的“效率权重”,将随机数部分转移出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php

namespace App\Console\Commands;

use Carbon\Carbon;
use Illuminate\Console\Command;

class DemoCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'demo:test';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
// 图表内容
$headers = ['次数', '方案1:乘1000', '方案2:移位符', '方案3:乘以 1024', '补充测试:仅位移'];
$data = [
[0 => '第一次'],
[0 => '第二次'],
[0 => '第三次']
];

// 随机数提出
$integer = rand(1, 999);

// 每个方法执行三次
for ($count = 0; $count < 3; $count++) {
$start = Carbon::now()->getPreciseTimestamp();
for ($i = 0; $i < 10000000; $i++) {
$result = $integer * 1000;
}
$end = Carbon::now()->getPreciseTimestamp();
$data[$count][] = ($end - $start) / 1000000 . '秒';
}

for ($count = 0; $count < 3; $count++) {
$start = Carbon::now()->getPreciseTimestamp();
for ($i = 0; $i < 10000000; $i++) {
$result = ($integer << 10) - ($integer * 24);
}
$end = Carbon::now()->getPreciseTimestamp();
$data[$count][] = ($end - $start) / 1000000 . '秒';
}

for ($count = 0; $count < 3; $count++) {
$start = Carbon::now()->getPreciseTimestamp();
for ($i = 0; $i < 10000000; $i++) {
$result = ($integer * 1024) - ($integer * 24);
}
$end = Carbon::now()->getPreciseTimestamp();
$data[$count][] = ($end - $start) / 1000000 . '秒';
}

for ($count = 0; $count < 3; $count++) {
$start = Carbon::now()->getPreciseTimestamp();
for ($i = 0; $i < 10000000; $i++) {
$result = $integer << 10;
}
$end = Carbon::now()->getPreciseTimestamp();
$data[$count][] = ($end - $start) / 1000000 . '秒';
}
$this->table($headers, $data);
}
}

运算结果

多次运行并且调整前后顺序,均得到一个较为稳定的结果:

image01

这个结果在意料之中,但又不完全在。位移符<<从理论上来讲和一般的乘法有着本质上的不同,应该有不错的效率提升。它之所以会比$integer * 1000效率低,应该是被第二步运算 - $integer * 24拖累了,但是位移符却也没有与*1024拉开差距。为了证实这一点,我又补上了单独进行位移计算的测试。结果证明:

位移符在PHP这里就仅仅是一个普通的乘除法计算而已。

GO

代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
func main() {
// 计算次数
maxI := 10000000

rand.Seed(time.Now().UnixNano())
integer := rand.Intn(999)

echoString := ""

fmt.Println("方案1:乘1000")
for count := 0; count < 3; count++ {
// 开始
start := time.Now()
echoString += "第" + strconv.Itoa(count+1) + "次:"

for i := 0; i < maxI; i++ {
_ = integer * 1000
}

// 结束
end := time.Now()

betweenTime := float64(end.Sub(start).Nanoseconds()) / 1000000000

echoString += strconv.FormatFloat(betweenTime, 'f', 10, 64) + "秒"

}

fmt.Println(echoString)

echoString = ""

fmt.Println("方案2:位移符")
for count := 0; count < 3; count++ {
// 开始
start := time.Now()
echoString += "第" + strconv.Itoa(count+1) + "次:"

for i := 0; i < maxI; i++ {
_ = (integer << 10) - (integer * 24)
}

// 结束
end := time.Now()

betweenTime := float64(end.Sub(start).Nanoseconds()) / 1000000000

echoString += strconv.FormatFloat(betweenTime, 'f', 10, 64) + "秒"

}

fmt.Println(echoString)

echoString = ""

fmt.Println("方案3:乘以1024")
for count := 0; count < 3; count++ {
// 开始
start := time.Now()
echoString += "第" + strconv.Itoa(count+1) + "次:"

for i := 0; i < maxI; i++ {
_ = (integer * 1024) - (integer * 24)
}

// 结束
end := time.Now()

betweenTime := float64(end.Sub(start).Nanoseconds()) / 1000000000

echoString += strconv.FormatFloat(betweenTime, 'f', 10, 64) + "秒"

}

fmt.Println(echoString)
}

运算结果

运行结果同样不出所料。

image01

不过横向对比一下,PHP的计算效率被GO秒的渣渣都不剩了。身为一名从PHP入行编程,并且现在的主要技术栈和生产能力依旧在PHP身上的我,不免产生一种好可怜啊的悲凉。

位移符可以帮助我们理解计算机的原理,但是在GO这类编译型语言这里,编译器已经帮你完成了位移符能完成的计算。