gpt4 book ai didi

PHP:以最快或/和最有效的方式编写大量小文件

转载 作者:塔克拉玛干 更新时间:2023-11-03 05:56:29 24 4
gpt4 key购买 nike

想象一下,一个事件将有 10,000 到 30,000 个文件,每个文件大约 4kb。

而且,将有几个事件同时运行。 10 顶。

目前,我将采用通常的方式:file_put_contents .

它完成了工作,但速度很慢,而且它的 php 进程一直占用 100% 的 CPU 使用率。
fopen, fwrite, fclose ,嗯,结果类似于file_put_contents .

我试过一些异步 io 的东西,比如 php eioswoole .

它更快,但一段时间后会产生“太多打开的文件”。
php -r 'echo exec("ulimit -n");'结果是 800000。

任何帮助,将不胜感激!

好吧,这有点尴尬……你们是对的,瓶颈在于它如何生成文件内容……

最佳答案

我假设您不能遵循 SomeDude 关于使用数据库的非常好的建议,并且您已经执行了可以执行的硬件调整(例如增加缓存、增加 RAM 以避免交换颠簸、购买 SSD 驱动器)。

我会尝试将文件生成卸载到不同的进程。

你可以例如安装Redis,将文件内容存入keystore,非常快。然后,一个不同的并行进程可以从 keystore 中提取数据,将其删除,然后写入磁盘文件。

这会从主要 PHP 进程中删除所有磁盘 I/O,并让您监控积压(有多少 key 对仍未刷新:理想情况下为零)并专注于内容生成中的瓶颈。您可能需要一些额外的 RAM。

另一方面,这与写入 RAM 磁盘没有太大区别。您还可以将数据输出到 RAM 磁盘,它可能会更快:

# As root
mkdir /mnt/ramdisk
mount -t tmpfs -o size=512m tmpfs /mnt/ramdisk
mkdir /mnt/ramdisk/temp
mkdir /mnt/ramdisk/ready
# Change ownership and permissions as appropriate

在 PHP 中:
$fp = fopen("/mnt/ramdisk/temp/{$file}", "w");
fwrite($fp, $data);
fclose($fp);
rename("/mnt/ramdisk/temp/{$file}", "/mnt/ramdisk/ready/{$file}");

然后有一个不同的进程(crontab?还是持续运行守护进程?)将文件从 RAM 磁盘的“就绪”目录移动到磁盘,然后删除 RAM 就绪文件。

文件系统

所需时间 创建文件 取决于目录中的文件数量,具有各种依赖函数,它们本身依赖于文件系统。 ext4、ext3、zfs、btrfs 等将表现出不同的行为。具体来说,如果文件数量超过某个数量,您可能会遇到明显的减速。

因此,您可能想尝试在一个目录中定时创建大量示例文件,看看这个时间是如何随着数量的增长而增长的。请记住,访问不同目录会降低性能,因此不建议立即使用大量子目录。
<?php
$payload = str_repeat("Squeamish ossifrage. \n", 253);
$time = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$fp = fopen("file-{$i}.txt", "w");
fwrite($fp, $payload);
fclose($fp);
}
$time = microtime(true) - $time;
for ($i = 0; $i < 10000; $i++) {
unlink("file-{$i}.txt");
}
print "Elapsed time: {$time} s\n";

在我的系统上创建 10000 个文件需要 0.42 秒,但创建 100000 个文件 (10x) 需要 5.9 秒,而不是 4.2 秒。另一方面,在 8 个单独的目录中创建这些文件的八分之一(我发现的最佳折衷方案)需要 6.1 秒,因此不值得。

但是假设创建 300000 个文件需要 25 秒而不是 17.7 秒;将这些文件分成 10 个目录可能需要 22 秒,因此值得拆分目录。

并行处理:r 策略

TL;DR 这在我的系统上效果不佳,尽管您的里程可能会有所不同 .如果要做的操作是 长篇 (在这里它们不是)并且与主进程的绑定(bind)不同,那么将它们各自卸载到不同的线程可能是有利的,前提是您不会产生太多线程。

您将需要 pcntl functions安装。
$payload    = str_repeat("Squeamish ossifrage. \n", 253);

$time = microtime(true);
for ($i = 0; $i < 100000; $i++) {
$pid = pcntl_fork();
switch ($pid) {
case 0:
// Parallel execution.
$fp = fopen("file-{$i}.txt", "w");
fwrite($fp, $payload);
fclose($fp);
exit();
case -1:
echo 'Could not fork Process.';
exit();
default:
break;
}
}
$time = microtime(true) - $time;
print "Elapsed time: {$time} s\n";

(花哨的名字 r strategy 取自生物学)。

在这个例子中,如果与每个 child 需要做的事情相比,产卵时间是灾难性的。因此,整体处理时间猛增。有了更复杂的子级,事情会变得更好,但你必须小心不要把脚本变成一个 fork 炸弹。

如果可能的话,一种可能性是将要创建的文件分成每个 10% 的块。然后每个 child 都会 更改其工作目录 使用 chdir(),并在不同的目录中创建其文件。这将消除在不同子目录中写入文件的惩罚(每个子目录在其当前目录中写入),同时从写入更少的文件中受益。在这种情况下,在子级中使用非常轻量级的 I/O 绑定(bind)操作,该策略再次不值得(我的执行时间增加了一倍)。

并行处理:K策略

TL; DR 这更复杂,但效果很好......在我的系统上。您的里程可能会有所不同 .
虽然 r 策略涉及许多“即发即弃”的线程,但 K 策略需要一个有限的(可能是一个) child ,并且需要谨慎地培养。在这里,我们将所有文件的创建卸载到一个并行线程,并通过套接字与其通信。
$payload    = str_repeat("Squeamish ossifrage. \n", 253);

$sockets = array();
$domain = (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' ? AF_INET : AF_UNIX);
if (socket_create_pair($domain, SOCK_STREAM, 0, $sockets) === false) {
echo "socket_create_pair failed. Reason: ".socket_strerror(socket_last_error());
}
$pid = pcntl_fork();
if ($pid == -1) {
echo 'Could not fork Process.';
} elseif ($pid) {
/*parent*/
socket_close($sockets[0]);
} else {
/*child*/
socket_close($sockets[1]);
for (;;) {
$cmd = trim(socket_read($sockets[0], 5, PHP_BINARY_READ));
if (false === $cmd) {
die("ERROR\n");
}
if ('QUIT' === $cmd) {
socket_write($sockets[0], "OK", 2);
socket_close($sockets[0]);
exit(0);
}
if ('FILE' === $cmd) {
$file = trim(socket_read($sockets[0], 20, PHP_BINARY_READ));
$len = trim(socket_read($sockets[0], 8, PHP_BINARY_READ));
$data = socket_read($sockets[0], $len, PHP_BINARY_READ);
$fp = fopen($file, "w");
fwrite($fp, $data);
fclose($fp);
continue;
}
die("UNKNOWN COMMAND: {$cmd}");
}
}

$time = microtime(true);
for ($i = 0; $i < 100000; $i++) {
socket_write($sockets[1], sprintf("FILE %20.20s%08.08s", "file-{$i}.txt", strlen($payload)));
socket_write($sockets[1], $payload, strlen($payload));
//$fp = fopen("file-{$i}.txt", "w");
//fwrite($fp, $payload);
//fclose($fp);
}
$time = microtime(true) - $time;
print "Elapsed time: {$time} s\n";

socket_write($sockets[1], "QUIT\n", 5);
$ok = socket_read($sockets[1], 2, PHP_BINARY_READ);
socket_close($sockets[1]);

这在很大程度上取决于系统配置 .例如,在单处理器、单核、非线程 CPU 上,这很疯狂——您至少会使总运行时间增加一倍,但更有可能它会慢三到十倍。

所以这绝对不是在旧系统上拉皮条的方法。

在现代多线程 CPU 上,假设主要内容创建循环受 CPU 限制,您可能会遇到相反的情况 - 脚本运行速度可能快十倍。

在我的系统上,上面的“ fork ”解决方案的运行速度比 少一点快三倍 .我期待更多,但你来了。

当然,性能是否值得增加复杂性和维护,还有待评估。

坏消息

在进行上述实验时,我得出的结论是,在 Linux 中配置合理且性能良好的机器上创建文件是 快如 hell ,因此不仅很难挤出更多性能,而且如果您遇到缓慢,很可能与文件无关。尝试详细说明您如何创建该内容。

关于PHP:以最快或/和最有效的方式编写大量小文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39436975/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com