11

我有一些非常大的数据文件,出于业务原因,我必须进行大量的字符串操作(替换字符和字符串)。这是不可避免的。替换的数量达到数十万。

它花费的时间比我想要的要长。PHP 通常非常快,但我正在做很多这样的字符串操作,以至于它变慢了,并且脚本执行需要几分钟。这很痛苦,因为脚本经常运行。

我做了一些测试,发现str_replace最快,其次是strstr,其次是preg_replace。我还尝试了单独的 str_replace 语句以及构造模式和替换数组。

我正在玩弄隔离字符串操作操作并用不同的语言编写的想法,但我不想花时间在那个选项上,只是发现改进可以忽略不计。另外,我只知道 Perl、PHP 和 COBOL,所以对于任何其他语言,我都必须先学习它。

我想知道其他人是如何解决类似问题的?

我已经搜索过,我认为这不会重复任何现有问题。

4

9 回答 9

1

好吧,考虑到在 PHP 中某些字符串操作比数组操作要快,而且您仍然对它的速度不满意,您可以按照您提到的那样编写外部程序,可能使用一些“低级”语言。我会推荐 C/C++。

于 2012-12-04T11:02:16.803 回答
1

有两种处理方法,IMO:

  • [简单]在后台进程中预先计算一些通用替换并将它们存储在数据库/文件中(这个技巧来自一个游戏开发,其中所有的正弦和余弦都预先计算一次,然后存储在 RAM 中)。但是,您很容易在这里遇到维度灾难;
  • [不太容易]用 C++ 或其他快速且可编译的编程语言实现替换工具,然后使用它。Sphinx是用 C++ 实现的大型文本数据集的快速操作工具的一个很好的例子。
于 2012-12-04T11:11:27.230 回答
1

如果您允许在多次执行中处理替换,您可以创建一个处理每个文件的脚本,临时创建具有重复内容的替换文件。这将允许您将数据从一个文件提取到另一个文件,处理副本 - 然后合并更改,或者如果您使用流缓冲区,您可能能够记住每一行,因此可以跳过复制/合并步骤。

但问题可能是您处理文件而不完成它,使其混合。因此临时文件是合适的。

这将允许脚本运行多次,仍然需要进行更改,您所需要的只是一个临时文件,该文件记住已处理的文件。

于 2012-12-04T11:12:04.953 回答
1

限制因素是关于 PHP 重建字符串。考虑:

$out=str_replace('bad', 'good', 'this is a bad example');

在字符串中定位 'bad' 是一种成本相对较低的操作,但为了为替换腾出空间,PHP 必须向上移动每个字符 e、l、p、m、a、x、e、写入新值之前的空格。

为 needle 和 haystack 传递数组将提高性能,但效果不如预期。

AFAIK,PHP 没有低级内存访问功能,因此必须用不同的语言编写最佳解决方案,将数据分成“页面”,可以拉伸以适应变化。您可以尝试使用 chunk_split 将字符串分成更小的单元(因此每次替换需要更少的内存处理)。

另一种方法是将其转储到文件中并使用 sed (这仍然一次操作一个搜索/替换),例如

sed -i 's/good/bad/g;s/worse/better/g' file_containing_data
于 2012-12-04T11:19:20.350 回答
0

由于您了解 Perl,我建议您使用正则表达式在 perl 中进行字符串操作,并在 PHP 网页中使用最终结果。

这似乎更好,原因如下

  1. 你已经知道 Perl
  2. Perl 更好地处理字符串

您可以仅在必要时使用 PHP。

于 2012-12-06T09:23:45.423 回答
0

我认为问题是你为什么经常运行这个脚本?您是一遍又一遍地对相同的数据执行计算(字符串替换),还是每次都对不同的数据执行计算?

如果答案是前者,那么在 PHP 端提高性能就没有什么可做的了。您可以通过其他方式提高性能,例如使用更好的硬件(SSD 用于更快地读取/写入文件)、多核 CPU 以及将数据分解为同时运行多个脚本以同时处理数据的小块,以及更快的 RAM (即更高的总线速度)。

如果答案是后者,那么您可能需要考虑使用诸如 memcached 或 reddis(键/值缓存存储)之类的东西来缓存结果,以便您只能执行一次计算,然后它只是从内存中线性读取,这非常便宜且几乎不涉及 CPU 开销(您也可以在此级别使用 CPU 缓存)。

PHP 中的字符串操作已经很便宜了,因为 PHP 字符串本质上只是字节数组。在将文件读入内存并将其存储在字符串中时,PHP 几乎没有任何开销。如果您有一些示例代码来演示您在哪里看到性能问题和一些基准数字,我可能会有一些更好的建议,但现在看起来您需要根据您的潜在需求重构您的方法。

例如,当您在不同情况下处理数据时,需要单独考虑 CPU 和 I/O 成本。I/O 涉及阻塞,因为它是一个系统调用。这意味着您的 CPU 必须等待更多数据通过线路(同时您的磁盘将数据传输到内存)才能继续处理或计算该数据。你的 CPU 总是比内存快得多,而内存总是比磁盘快得多。

这是一个简单的基准来向您展示差异:

/* First, let's create a simple test file to benchmark */
file_put_contents('in.txt', str_repeat(implode(" ",range('a','z')),10000));

/* Now let's write two different tests that replace all vowels with asterisks */

// The first test reads the entire file into memory and performs the computation all at once

function test1($filename, $newfile) {
    $start = microtime(true);
    $data = file_get_contents($filename);
    $changes = str_replace(array('a','e','i','o','u'),array('*'),$data);
    file_put_contents($newfile,$changes);
    return sprintf("%.6f", microtime(true) - $start);
}

// The second test reads only 8KB chunks at a time and performs the computation on each chunk

function test2($filename, $newfile) {
    $start = microtime(true);
    $fp = fopen($filename,"r");
    $changes = '';
    while(!feof($fp)) {
        $changes .= str_replace(array('a','e','i','o','u'),array('*'),fread($fp, 8192));
    }
    file_put_contents($newfile, $changes);
    return sprintf("%.6f", microtime(true) - $start);
}

上面的两个测试做同样的事情,但是当我使用更少量的数据(在这个测试中大约 500KB )时, Test2对我来说明显更快。

这是您可以运行的基准测试...

// Conduct 100 iterations of each test and average the results
for ($i = 0; $i < 100; $i++) {
    $test1[] = test1('in.txt','out.txt');
    $test2[] = test2('in.txt','out.txt');
}
echo "Test1 average: ", sprintf("%.6f",array_sum($test1) / count($test1)), "\n",
     "Test2 average: ", sprintf("%.6f\n",array_sum($test2) / count($test2));

对我来说,上面的基准测试给出了Test1 average: 0.440795and Test2 average: 0.052054,这是一个数量级的差异,这只是对 500KB 的数据进行测试。现在,如果我将此文件的大小增加到大约 50MB,Test1实际上证明会更快,因为每次迭代的系统 I/O 调用更少(即我们只是在 Test1 中线性地从内存中读取),但更多的 CPU 成本(即我们每次迭代执行更大的计算)。事实证明,CPU 一次能够处理比 I/O 设备通过总线发送的数据量大得多的数据。

因此,在大多数情况下,这不是一个万能的解决方案。

于 2012-12-06T04:20:27.950 回答
0

这种操纵必须即时发生吗?如果没有,我可以建议预处理......也许通过cron作业。

定义您将使用的规则。它只是一个 str_replace 还是几个不同的?你必须一次性完成整个文件吗?或者你能把它分成多批吗?(例如一次一半的文件)

一旦定义了规则,就决定何时进行处理。(例如每个人上班前的早上 6 点)

然后你可以设置一个作业队列。我已经使用 apache 的 cron 作业在给定的时间安排上运行我的 php 脚本。

对于我不久前从事的一个项目,我有一个这样的设置:

7:00 - pull 10,000 records from mysql and write them to 3 separate files.
7:15 - run a complex regex on file one.
7:20 - run a complex regex on file two.
7:25 - run a complex regex on file three.
7:30 - combine all three files into one.
8:00 - walk into the metting with the formatted file you boss wants. *profit*

希望这可以帮助您思考...

于 2012-12-06T21:14:16.350 回答
0

如果您只需执行一次此操作并且必须替换为静态内容,您可以使用 Dreamwaver 或其他编辑器,因此您不需要 PHP。它会快得多。

尽管如此,如果您确实需要使用 PHP 动态执行此操作(您需要数据库记录或其他),您可以通过exec使用 shell 命令- google search for search-replace

于 2012-12-04T11:12:03.140 回答
0

有可能你已经用 PHP 碰壁了。PHP 很棒,但在某些领域它失败了,例如处理大量数据。你可以做几件事:

  1. 使用多个 php 进程来完成任务(2 个进程可能需要一半的时间)。
  2. 安装更快的 CPU。
  3. 在多台机器上进行处理。
  4. 使用编译语言处理数据(Java、C、C++ 等)
于 2012-12-05T23:02:21.840 回答