PHP - Манипуляция медленными строками

У меня есть очень большие файлы данных, и по коммерческим причинам я должен выполнять обширные манипуляции с строками (заменяя символы и строки). Это неизбежно. Количество замен составляет сотни тысяч.

Это займет больше времени, чем хотелось бы. PHP, как правило, очень быстрый, но я делаю так много этих строковых манипуляций, что он замедляется, а выполнение script выполняется в минутах. Это боль, потому что script выполняется часто.

Я провел некоторое тестирование и обнаружил, что str_replace быстрее, затем strstr, а затем preg_replace. Я также пробовал отдельные инструкции str_replace, а также создавал массивы шаблонов и замен.

Я играю с идеей изолировать операцию манипуляции строкой и писать на другом языке, но я не хочу вкладывать время в этот параметр, только чтобы найти, что улучшения незначительны. Кроме того, я знаю только Perl, PHP и COBOL, поэтому для любого другого языка мне нужно будет сначала изучить его.

Мне интересно, как другие люди подошли к подобным проблемам?

Я искал, и я не считаю, что это дублирует любые существующие вопросы.

Ответ 1

Хорошо, учитывая, что в PHP некоторые операции с String быстрее, чем операция с массивом, и вы все еще не удовлетворены своей скоростью, вы можете написать внешнюю программу, как вы упомянули, возможно, на каком-то языке "более низкого уровня". Я бы рекомендовал C/С++.

Ответ 2

Есть два способа обработки этого: IMO:

  • [easy] Предкоммутировать некоторые общие замены в фоновом процессе и хранить их в DB/файле (этот трюк исходит от гамедева, где все синусы и косинусы предварительно вычисляются один раз, а затем сохраняются в ОЗУ). Однако вы можете легко напасть на проклятие размерности,
  • [не так просто] Внедрите инструмент замены на С++ или другой быстрый и компилируемый язык программирования и используйте его впоследствии. Sphinx - хороший пример быстрого инструмента для обработки больших наборов текстовых данных, реализованных на С++.

Ответ 3

Если вы позволите замене обрабатывать несколько запусков, вы можете создать script, обрабатывающий каждый файл, временно создавая файлы замещения с дублирующимся контентом. Это позволит вам извлекать данные из одного файла в другой, обрабатывать копию, а затем объединять изменения, или если вы используете буфер потока, вы можете запомнить каждую строку, чтобы шаг копирования/слияния можно было пропустить.

Проблема может заключаться в том, что вы обрабатываете файл без его завершения, делая его смешанным. Поэтому подходит временный файл.

Это позволит script запускать столько раз, пока все еще будут внесены изменения, все, что вам нужно, это временный файл, который запоминает, какие файлы были обработаны.

Ответ 4

Ограничивающий фактор - это перестройка PHP строк. Рассмотрим:

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

Это относительно дешевая операция для определения "плохого" в строке, но для того, чтобы освободить место для замены, PHP затем должен двигаться вверх, каждый из символов e, l, p, m, a, x, e, пробел перед записью в новом значении.

Передача массивов для иглы и стога сена повысит производительность, но не настолько, насколько это возможно.

AFAIK, PHP не имеет функций доступа к низкоуровневому доступу к памяти, поэтому оптимальное решение должно быть написано на другом языке, разделяя данные на "страницы", которые можно растянуть для внесения изменений. Вы можете попробовать это с помощью chunk_split, чтобы разделить строку на более мелкие единицы (следовательно, для каждой замены потребуется меньше жонглирования).

Другим подходом было бы свалить его в файл и использовать sed (это все еще работает с одним поиском/заменой за раз), например.

sed -i 's/good/bad/g;s/worse/better/g' file_containing_data

Ответ 5

Если вам нужно выполнить эту операцию только один раз, и вам нужно заменить статическим контентом, вы можете использовать Dreamwaver или другой редактор, поэтому вам не понадобится PHP. Это будет намного быстрее.

Тем не менее, если вам нужно динамически работать с PHP (вам нужны записи базы данных или другие), вы можете использовать команды оболочки через exec - google search for search-replace

Ответ 6

Возможно, вы столкнулись с стеной с PHP. PHP замечательный, но в некоторых областях он терпит неудачу, например, обрабатывает множество данных. Есть несколько вещей, которые вы могли бы сделать:

  • Для выполнения задачи используйте более одного процесса php (2 процесса потенциально могут занимать половину времени).
  • Установите более быстрый процессор.
  • Выполняйте обработку на нескольких машинах.
  • Скомпилированный язык для обработки данных (Java, C, С++ и т.д.)

Ответ 7

Я думаю, вопрос в том, почему вы часто запускаете этот script? Вы выполняете вычисления (замены строк) на одни и те же данные снова и снова, или вы делаете это каждый раз по разным данным?

Если ответ первый, то вы не можете сделать больше, чтобы повысить производительность на стороне PHP. Вы можете повысить производительность другими способами, такими как использование более совершенного оборудования (SSD для более быстрого чтения/записи в файлах), многоядерных процессоров и разбиения данных на более мелкие части, работающие одновременно с несколькими сценариями, для одновременной обработки данных и более быстрой RAM (т.е. более высокие скорости шины).

Если ответ будет последним, тогда вы можете захотеть рассмотреть вопрос о кешировании результата, используя что-то вроде memcached или reddis (хранилища ключей/значений), так что вы можете выполнять вычисления только один раз, а затем просто читать строки из памяти, который очень дешев и практически не требует затрат на процессор (вы также можете использовать кэш CPU на этом уровне).

Манипуляции строк в PHP уже дешевы, потому что строки PHP по существу представляют собой только массивы байтов. Практически нет накладных расходов на PHP при чтении файла в память и хранении его в строке. Если у вас есть пример кода, который демонстрирует, где вы видите проблемы с производительностью, и некоторые номера контрольных точек, у меня могут быть некоторые рекомендации, но сейчас это похоже на то, что вам нужен рефакторинг вашего подхода, основанный на ваших базовых потребностях.

Например, есть затраты на CPU и I/O, которые следует учитывать индивидуально, когда вы имеете дело с данными в разных ситуациях. Ввод-вывод включает блокировку, так как это системный вызов. Это означает, что ваш процессор должен дождаться появления большего количества данных по проводу (пока ваш диск передает данные в память), прежде чем он сможет продолжать обрабатывать или вычислять эти данные. Ваш процессор всегда будет намного быстрее, чем память, а память всегда намного быстрее, чем диск.

Вот простой тест, чтобы показать вам разницу:

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

/* Now let 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);
}

В приведенных выше двух тестах делается то же самое, но Test2 оказывается значительно быстрее для меня, когда я использую меньшие объемы данных (примерно 500 КБ в этом тесте).

В этом тесте вы можете запустить...

// 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.440795 и Test2 average: 0.052054, что представляет собой разницу по порядку величины и просто тестирование на 500 Кбайт данных. Теперь, если я увеличиваю размер этого файла примерно до 50 МБ Test1, на самом деле оказывается более быстрым, так как на итерации меньше вызовов системного ввода-вывода (т.е. мы просто читаем из памяти линейно в Test1), но больше стоимости процессора (т.е. мы выполняем гораздо более крупные вычисления за итерацию). ЦП обычно оказывается способным обрабатывать гораздо большие объемы данных за раз, чем ваши устройства ввода/вывода могут отправлять по шине.

В большинстве случаев это не решение с одним размером.

Ответ 8

Поскольку вы знаете Perl, я бы предложил сделать строковые манипуляции в perl, используя регулярные выражения, и использовать конечный результат на веб-странице PHP.

Это выглядит лучше по следующим причинам.

  • Вы уже знаете Perl
  • Perl лучше выполняет обработку строк

Вы можете использовать PHP только там, где это необходимо.

Ответ 9

Неужели эта манипуляция должна произойти "на лету"? если нет, могу ли я предложить предварительную обработку... возможно, через работу 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*

надеюсь, что это поможет вам подумать...