4. 代码层面的性能优化:循环优化、函数调用优化、递归优化、字符串处理优化
代码层面的优化,说白了就是跟你的每一行代码较劲。我见过太多项目,架构设计得漂漂亮亮,结果一压测就崩,问题全出在这些细节上。今天咱们就聊聊四个最常见的性能杀手:循环、函数调用、递归和字符串处理。
4.1 循环优化:别让循环成为你的性能黑洞
循环是PHP里最容易被忽视的性能陷阱。你想想看,一个循环多执行一次,就是N倍的性能损耗。我在项目中遇到过一段代码,处理10万条数据要跑30秒,优化后直接降到2秒——差距就这么大。
4.1.1 减少循环内的计算
先看个反面教材:
// 糟糕的写法
for ($i = 0; $i < count($array); $i++) {
// 每次循环都调用 count()
}
为什么会这样?count() 每次循环都被调用一次。如果数组有10万条,那就多调用了10万次。改成这样:
// 优化后
$len = count($array);
for ($i = 0; $i < $len; $i++) {
// count() 只调用一次
}
4.1.2 使用 foreach 代替 for
我个人更推荐用 foreach。它比 for 快,而且代码更清晰。为什么?foreach 直接操作数组内部指针,省去了索引计算的开销。
// 推荐写法
foreach ($array as $value) {
// 处理逻辑
}
如果你需要同时获取键和值,也别用 $array[$i] 这种写法:
// 更高效的写法
foreach ($array as $key => $value) {
// 直接使用 $key 和 $value
}
4.1.3 避免在循环内创建对象
嗯,这里要注意。循环内频繁创建对象,GC(垃圾回收)会疯掉的。我曾经排查过一个内存泄漏问题,最后发现是循环里 new 了太多对象。
// 糟糕的做法
for ($i = 0; $i < 10000; $i++) {
$obj = new SomeClass();
$obj->doSomething();
}
// 优化方案:复用对象
$obj = new SomeClass();
for ($i = 0; $i < 10000; $i++) {
$obj->doSomething();
}
array_push() 代替 $array[] =,结果性能差了3倍。记住:$array[] = 比 array_push() 快,因为它少了一次函数调用。
4.2 函数调用优化:减少不必要的开销
函数调用本身是有成本的。每次调用都要压栈、传参、返回。虽然单次开销很小,但积少成多就很可观了。
4.2.1 避免在循环中调用函数
这个跟循环优化是联动的。看个例子:
// 糟糕的写法
for ($i = 0; $i < 1000; $i++) {
echo strtoupper($array[$i]);
}
// 优化后
$upper = 'strtoupper';
for ($i = 0; $i < 1000; $i++) {
echo $upper($array[$i]);
}
用变量函数调用,能省掉函数名解析的时间。虽然提升不大,但积少成多嘛。
4.2.2 使用内置函数代替自定义函数
PHP的内置函数是用C写的,性能远高于PHP写的自定义函数。能用的内置函数就别自己造轮子。
| 场景 | 自定义函数(慢) | 内置函数(快) |
|---|---|---|
| 数组去重 | 循环+判断 | array_unique() |
| 字符串拼接 | . 操作符 |
implode() |
| 数组查找 | in_array() |
isset() + 翻转数组 |
4.3 递归优化:小心栈溢出
递归写起来很优雅,但性能上是个坑。我见过一个递归遍历目录的脚本,目录深度超过1000层,直接报栈溢出。说白了,递归就是拿空间换时间——每次调用都要压栈。
4.3.1 递归 vs 迭代
能用迭代就别用递归。看个斐波那契数列的例子:
// 递归写法(性能差)
function fib($n) {
if ($n <= 1) return $n;
return fib($n - 1) + fib($n - 2);
}
// 迭代写法(性能好)
function fib($n) {
if ($n <= 1) return $n;
$prev = 0;
$curr = 1;
for ($i = 2; $i <= $n; $i++) {
$next = $prev + $curr;
$prev = $curr;
$curr = $next;
}
return $curr;
}
递归版本计算 fib(40) 可能要几秒,迭代版本毫秒级搞定。差距就是这么明显。
4.3.2 尾递归优化
PHP不支持尾递归优化,但我们可以手动模拟。把递归改成尾递归形式,虽然PHP不会自动优化,但至少代码更清晰:
// 尾递归形式
function factorial($n, $acc = 1) {
if ($n <= 1) return $acc;
return factorial($n - 1, $acc * $n);
}
4.4 字符串处理优化:别小看拼接
字符串操作在PHP里太常见了,但很多人不注意性能。你想想看,PHP的字符串是不可变的,每次拼接都会创建新字符串。
4.4.1 用 implode() 代替 . 拼接
这是最经典的优化。循环里用 . 拼接字符串,每次都会创建新字符串,性能极差。
// 糟糕的写法
$result = '';
foreach ($items as $item) {
$result .= $item . ',';
}
// 优化后
$result = implode(',', $items);
如果必须用循环拼接,那就用数组收集:
// 更高效的写法
$parts = [];
foreach ($items as $item) {
$parts[] = $item;
}
$result = implode(',', $parts);
4.4.2 使用单引号代替双引号
双引号字符串会解析变量,单引号不会。虽然差距不大,但能省则省:
// 双引号(慢)
$str = "Hello, $name!";
// 单引号(快)
$str = 'Hello, ' . $name . '!';
不过说实话,这个优化在现代PHP版本里效果已经不明显了。我更推荐用可读性好的写法。
4.4.3 字符串查找和替换
能用 strpos() 就别用正则。正则引擎启动成本很高:
// 正则(慢)
if (preg_match('/hello/', $str)) { ... }
// strpos(快)
if (strpos($str, 'hello') !== false) { ... }
strlen() 判断长度,避免对空字符串做无谓的操作。这招在处理用户输入时特别有用。
4.5 实战总结
说了这么多,其实核心就一句话:减少不必要的操作。不管是循环、函数调用、递归还是字符串处理,本质都是在跟CPU和内存较劲。
我建议你在写代码时养成几个习惯:
- 循环条件提前计算好
- 能用内置函数就别自己写
- 递归深度超过100层就考虑迭代
- 字符串拼接用数组+implode
这些优化单个看起来提升不大,但组合起来效果惊人。我记得有一次帮一个电商项目做优化,光是把循环里的 count() 提取出来,接口响应时间就降了30%。
嗯,代码层面的优化就聊到这儿。下一章咱们聊聊数据库层面的优化,那才是真正的重头戏。