每次运行 PHP 应用程序或其他软件时,它都会消耗内存。基本上,所有应用程序都需要内存来运行。随着所有这些应用程序争夺资源,计算机必须有效地分配内存。这使我们来到了内存管理过程。那是什么?

内存管理涉及释放、分配、组织和优化可用计算机内存,以确保程序正常运行。大多数情况下,计算机自动进行内存管理,但作为一名 PHP 开发者,你有责任管理你应用的内存占用。

本文将讨论您可以使用的技术来降低 PHP 应用程序的内存占用。

为什么内存管理很重要?

PHP 应用程序通常有大约 128MB 的内存限制。如果您的应用程序超过此限制,您将遇到以下致命错误:

Fatal error: Allowed memory size of 94371840 bytes exhausted (tried to allocate 5072 bytes)

上述错误会导致您的应用程序崩溃,这可能会非常令人沮丧。在某些情况下,您可能会遇到卡顿和冻结,最终影响整体用户体验。

尽管您可以扩展 PHP 应用的内存限制,但这可能是一个漫长且复杂的过程,应该只作为最后的手段。一个更好的选择是审查您的应用程序并纠正可能导致高内存消耗的区域。

您可以使用以下方法找到您的 PHP 应用程序消耗的内存量及其峰值使用情况:

// Current memory usage
$memory_usage = memory_get_usage();

// Peak memory usage
$memory_peak = memory_get_peak_usage();

接下来,让我们看看 PHP 应用程序中内存使用量高的常见原因。

常见高内存使用原因

以下是 PHP 应用程序中内存使用量高的常见原因。

内存泄漏

内存泄漏发生在开发者为其应用程序分配了一定百分比的内存,但在特定任务完成后忘记释放它。

如果您的应用程序在本地服务器上运行,您将注意到性能下降,尤其是在运行耗时任务时。这是因为可用的内存相当小,而计算机仍然需要将其分配给其他应用程序。

PHP 库和扩展

PHP 库很重要,因为它们允许我们在不写很多代码的情况下为我们的项目添加功能。然而,过度依赖 PHP 库有其缺点。例如,一些扩展可能会消耗更多内存。更糟糕的是,由于代码不是开源的,你对它们几乎没有控制权。

PHP 版本更新失败

在某些情况下,您的 PHP 应用程序可能仅仅因为您使用的是旧版本的 PHP 而消耗更多内存。这些版本可能存在错误或效率低下的代码,从而影响应用程序的性能。

运行过多脚本

网站同时执行过多脚本会导致内存使用过高,尤其是如果它们包含低效代码的话。一些 PHP 脚本具有无限循环或定时器,这意味着它们会长时间运行。这类网站崩溃的可能性很高。此外,用户界面的明显延迟会导致糟糕的用户体验。

现在我们已经了解了 PHP 应用程序内存消耗的常见原因,接下来我们将讨论如何在下一节中减少您的应用程序内存使用。

如何减少 PHP 应用的内存占用

在您创建或部署 PHP 应用程序时,以下是一些减少其内存占用大小的技巧。

修复内存泄漏

PHP 应用程序可能会消耗更多内存,仅仅是因为代码中的某些部分仍在运行并消耗资源,但它们已经不再需要。随着应用程序的增长,这些部分可能会降低性能并因内存使用过高而导致致命崩溃。

第一步修复内存泄漏是确定哪些脚本消耗了异常高的内存。您可以使用以下代码生成脚本的日志。

auto_append_file
memory_get

一旦生成日志,请审查它们并确定哪些消耗更多内存。

您也可以使用以下方法确定脚本消耗的确切内存量:

//Currently used memory
memory_get_usage();

//Peak memory usage
memory_get_peak_usage();

例如,以下计算斐波那契序列的脚本仅消耗 389KB 内存。然而,请注意,随着斐波那契序列的增长,内存量可能会增加。

$previousValues=array(0,1);

function fibonacci($n) {
    global $previousValues;
  if(array_key_exists($n, $previousValues) ){
    return $previousValues[$n];
  }
  else {
    if($n > 1) {
      $result = fibonacci($n-1) + fibonacci($n-2);
      $previousValues[$n] = $result;
      return $result;
    }
    return $n;
  }
}

$number = 9;
echo fibonacci($number);
/* Displaying used memory */
echo '<br>Memory consumed: ' . round(memory_get_usage() / 1024) . 'KB<br>';
/* Displaing peak memory usage */
echo 'Peak usage: ' . round(memory_get_peak_usage() / 1024) . 'KB of memory.<br><br>';

当你确定一个脚本确实消耗了更多内存时,遍历代码并尝试识别负责的部分(内存泄漏)。你可以注释掉代码中的某些部分(你怀疑是内存密集型的),然后再次执行代码。优化导致高内存泄漏的函数,并再次运行测试。

读取文件和数据库记录 在某些 PHP 应用程序中,您可能需要读取、创建、修改和删除文件。尽管这些操作很重要,但它们可能导致您的应用程序内存使用量过高,尤其是在实现不正确的情况下。

一个在处理大文件时节省内存的小技巧是逐行读取其内容,而不是将整个文件保存在内存中。让我们看看原因。

file_get_contents 方法用于检索文件内容并将其存储在 string 中。当涉及小文件时,此方法执行速度快,但随着文件大小的增长,您的应用程序可能需要更多内存,最终在达到限制时崩溃。

以下是 file_get_contents 方法的示例图:

//loading a large text file (1GB) using the file_get_contents method
$myfile = file_get_contents('myverylargefile.txt');
echo $myfile;

当你运行上述代码时,你将遇到以下错误:

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 529084788 bytes) in index.php

上述错误发生是因为 PHP 尝试在内存中加载大约 1GB 的文件,但脚本允许的限制是大约 128MB。

我们可以通过简单地逐行读取大文件,使用 fopenfgetsfeof 方法,来避免上述错误并降低我们的内存占用,如下所示。

//File path.
$file = 'myverylargefile.log';

//Opening the file in "reading only" mode.
$fileHandler = fopen($file, "r");

//We throw an exception in case if we do find the file.
if($fileHandler === false){
    throw new Exception('Cannot find: ' . $file);
}

//We use a while loop to go through the file
while(!feof($fileHandler)) {
    //We read the current line
    $line = fgets($fileHandler);
    //We can save line in an array or run any operation.
}

//Close the file handler when done to avoid file corruption
fclose($fileHandler);

当从数据库检索记录时,同样适用此原则。而不是一次性获取所有条目,而是遍历数据库并逐个检索记录。

在以下示例中,当同时从数据库中检索所有数据时,大约会消耗 642kbs 的内存:

// Fetching all records at once
$results = $stmt->fetchAll();
foreach ($results as $r) { print_r($r); }

输出:

PEAK USAGE - 642kbs

相比之下,使用以下代码逐个从数据库中检索记录时,将消耗 626kbs 的内存。虽然这看起来微不足道,但它可以为您的应用程序带来巨大的性能提升,尤其是在涉及大量数据时。

while ($r = $databasestatement->fetch()) { 
  print_r($r); 
}

输出:

PEAK USAGE - 626kbs

以下是检索 MySQL 数据库记录的完整代码。请注意,一次性检索所有数据库记录的低效代码已被注释掉。

//Database connection details
$db_host = "localhost";
$db_name = "products";
$db_charset = "utf8mb4";
$db_user = "root";
$db_password = "";


// Establishing a database connection
$conn = new PDO(
  "mysql:host=".$db_host.";dbname=".$db_name.";charset=".$db_charset,
  $db_user, $db_password, [
  PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);

// Preparing SQL statements
$databasestatement = $conn->prepare("SELECT * FROM `products`");
$databasestatement->execute();

// Unrecommended method - Fetching all records 
// $results = $stmt->fetchAll();
// foreach ($results as $r) { print_r($r); }

// Preferred method - Fetch records line by line
while ($r = $databasestatement->fetch()) { 
  print_r($r); 
}

// Once done retrieving records, close the database connection
if ($databasestatement !== null) { $databasestatement = null; }
if ($conn !== null) { $conn = null; }

// (G) PEAK MEMORY USED
echo "PEAK USAGE - " .  round(memory_get_peak_usage() / 1024). "kbs" ;

使用最新的 PHP 版本

始终确保您使用最新版本的 PHP 构建应用程序。最新版本不仅使您的网站更节省内存,而且通过解决漏洞,提高了整体安全性。

如果您有一个旧网站,您可以将现有的代码库升级到最新的 PHP 版本。

禁用未使用的插件

如上所述,过度依赖 PHP 库可能会导致您的应用程序消耗更多内存。这是因为您对它们几乎没有控制权。因此,建议您遍历您的项目,识别并禁用您不需要的插件。

您应该在将插件集成到项目中之前,研究和了解其他开发者对这些插件的评价。

结论

降低您的 PHP 应用程序的内存使用可以提升用户体验。不仅您的脚本和网页加载速度更快,而且网站崩溃、冻结或卡顿的可能性也较低。

今天减少您的应用程序内存占用,通过查找和消除内存泄漏、有效读取文件以及禁用未使用的脚本和库。