2012-12-04 4 views
11

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

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

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

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

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

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

+0

это большой вопрос. +1 от меня. Я вижу, вы используете файлы. Можете ли вы каким-то образом переключиться на базу данных? если нет, мы можем увидеть некоторые данные из файлов? – pregmatch

+0

Как строки считываются и обрабатываются? Вы сравнили замену строк с фактическим открытием файлов или потоков? Платформа? – Daniel

+0

В файлах нет ничего фантастического, это просто вопрос замены символов подчеркивания, удаления запятых, замены символов, отличных от UTF8, и т. Д. И т. Д. –

ответ

1

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

+0

перед тем, как начать писать собственный заменитель строк в C, я бы посмотрел на существующие инструменты, такие как 'sed' –

1

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

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

Даже если я предварительно вычислил, не нужно ли мне делать какой-то поиск во время выполнения, чтобы найти соответствующее заранее вычисленное значение, которое я хотел для каждого поиска/замены? –

+0

@ Симон Робертс, трудно сказать без каких-либо примеров ваших данных и замен, которые вам нужно применить. Предположим, что у вас есть данные .csv. Затем вы предварительно компилируете файлы, такие как data-no-underscore.csv, data-no-comma.csv, data-no-umlaut.csv, data-no-comma-underscores.csv, data-no-umlaut-underscores.csv и т. Д. Таким образом, поиск нужного файла - это дело конкатенации, скажем, сортировки по алфавиту со смещением фильтров на нужное имя набора данных. –

0

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

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

+1

Предполагается, что файлы доступны локально. – Daniel

+1

Я предполагаю, что это означало бы создание массивов данных для перехода к внешним функциям и разбора возвращаемого ввода. Возможно, это будет быстрее, чем делать все это в PHP-скрипте в первую очередь? –

+0

Вместо str_replace («mike», «george», $ string) вы можете заменить весь файл; это значит много – vectorialpx

1

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

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

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

+0

Я понимаю, что вы имеете в виду, но мой поток процессов является базовым: получить файл -> обработать его -> поместить данные в БД. Я думаю, это означает, что я должен обработать весь файл, прежде чем перейти к следующему шагу. Даже если бы я разделил операцию поиска/замены, мне все равно пришлось бы дождаться завершения всех частей, прежде чем продолжить (я думаю). –

1

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

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

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

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

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

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

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

А, это прекрасно объясняет, почему поиск намного быстрее, чем замена, спасибо. Жаль, что я не могу предсказать необходимые изменения, или я мог бы выполнить повторное назначение поиска и переменной, которое, как я полагаю, будет быстрее. –

0

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

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

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

+0

PHP действительно не поддерживает потоки, поэтому, чтобы вы делали это в PHP, выполните следующие действия: что-то вроде 'exec (" php /myFile.php pram1 param2>/dev/null 2> &1 &"); 'вы бы получили их вот так: в файле: '$ param1 = $ arg [1];' –

0

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

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

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

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

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

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

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

Вышеуказанных два теста сделать точно такие же вещи, но 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), но больше Стоимость процессора (т. Е. мы выполняем гораздо более крупные вычисления за итерацию). ЦП обычно оказывается способным обрабатывать гораздо большие объемы данных за раз, чем ваши устройства ввода/вывода могут отправлять по шине.

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

+0

Независимо от того, будут ли значения одинаковыми или разными каждый раз неизвестно, поэтому каждый раз требуется тест. Я попытался оценить перед заменой после прочтения ответа symcbean, но он на самом деле это немного увеличило время обработки - по-видимому, потому, что str_replace сам содержит тест соответствия, поэтому я дважды тестировал дважды. Время доступа к файлу - это не проблема (миллисекунды). Перед обработкой завершаются файлы с массивами. str_replace, а не строки, считанные непосредственно из файлов. Спасибо за подробный ответ –

0

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

Это кажется лучше по следующим причинам

  1. Вы уже знаете, Perl
  2. Perl делает строку обработки лучше

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

+0

Да, я думаю, это будет хорошим вариантом для меня. o запустите несколько тестов и посмотрите, насколько быстрее PERL. –

0

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

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

Как только ваши правила определены, вы решите, когда будете выполнять обработку. (например, до 6 часов, прежде чем все придут на работу)

, тогда вы можете настроить очередь заданий. Я использовал задания cron для apache для запуска моих 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* 

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

Смежные вопросы