За последние 24 часа нас посетили 35312 программистов и 1814 роботов. Сейчас ищут 788 программистов ...

Обработка больших файлов на PHP (до ~2 gb)

Тема в разделе "PHP для новичков", создана пользователем freelsd, 12 фев 2016.

  1. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Нужно обработать большой файл, посмотреть некоторые данные в строках и если нужно, добавить записи в бд. Как можно ускорить работу? Я так понимаю нужно использовать многопоточность и pthreads?
     
  2. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    многопоточность поможет тебе быстрее читать файл?
     
  3. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Поможет быстрее строки процессить и вставлять необходимые данные в бд. А какие еще варианты есть?
     
  4. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    просто "поточность", без много-. обработка в цепочку без полного чтения.
     
  5. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Сейчас опишу ситауцию, так понятней станет: из огромного фала читаются строки, в строках ищутся домены. Найденный домен нужно проверить по базе геоип (локальной или удаленно). Потому эти данные заносятся в бд. Если задачу разбить на несколько потоков, то не нужно ждать пока каждый домен по геоип пробьется и запись добавится в базу, это будет параллельно происходить. Хотелось бы этот процесс максимально ускорить. А обработка в цепочку это как?
     
  6. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    если у тебя много-процессорный комп + подходящий язык и алгоритм, позволяющий эффективно разнести потоки без блокировок, вот тогда может быть и много-…

    Добавлено спустя 7 минут 17 секунд:
    я объясню как НЕ в цепочку: допустим твои данные в XML и ты умеешь пользоваться SimpleXML. твой первый порыв будет сделать simplexml_load_file($filename), после чего всё умирает. то же самое с PHPExel и множеством других прекрасных инструментов. тупо не хватит памяти. придется искать способ подсасывать файл по частям.

    с твоим геоип может быть не настолько показательно, если ты сумеешь размазать файл по потокам. но реально на php и mysql ты не сможешь одновременно читать и писать много потоков ибо есть блокировки. а вот оверхеда хлебнешь.
     
  7. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Я поэтому про pthreads и спрашиваю. Например когда нужно было инфу с множества сайтов стянуть, многопоточность решала. А здесь все равно скорость получается не очень большая, хоть и поставил потоков под 300 и в бд максимальное кол-во соединений до 500. Скорость конечно лучше чем в одном каждую строку по очереди обрабатывать, но все равно не то. Да, и что за обработка в цепочку?
     
  8. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    забей. это разговор слепого с глухим.
     
  9. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Памяти у меня достаточно, на локальной машине 8 гб, на сервере вообще дофига. Можно конечно и построчно читать (это и есть в цепочку, только дошло) там сервер ссд, поэтому норм будет. А что за блокировки и какой оверхед? Я с такой задачей в первый раз сталкиваюсь. Интересно как ее решить.
     
  10. Ke1eth

    Ke1eth Активный пользователь

    С нами с:
    16 мар 2012
    Сообщения:
    1.073
    Симпатии:
    11
    Адрес:
    заблудилса
    Читай, дели на порции, запускай в отдельном треде дальше на обработку каждую, но это НЕ для PHP задача.
     
  11. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Я так примерно в тестовом варианте и делал - пихал по N строк в массив и этот массив отдавал треду. Но скорость все-равно не ахти. А почему задача не для php?
     
  12. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.559
    Симпатии:
    632
    Я не вижу проблемы в том чтобы плодить форки, знающие с какой по счету строки начать и сколько обработать. Хоть двести форков.
     
  13. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    Братья, чем рассуждать, сделайте. Будет интересно оценить.
    freelsd, ты начинай делать, а общество поможет.
     
  14. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    Почему-то мне кажется, от ТСа мы  н и ч е г о  не увидим ))) Предлагаю альтернативный открытый челлендж по быстрому разбору файла: viewtopic.php?f=47&t=56710
     
  15. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Ну я могу свое убожество забросить, только как оформлять парсинг строки в классе тредов? Просто чтение строки, выделение из нее мыла и запись в бд? Если устроит, то сегодня мб выложу кодес.

    Добавлено спустя 33 минуты 54 секунды:
    Вот схема тестовой бд:

    Код (PHP):
    1. CREATE table mail
    2. (
    3.     id int NOT NULL AUTO_INCREMENT,
    4.     mail varchar(255),
    5.     domain varchar(255),    
    6.     PRIMARY KEY(id)
    7.     
    8. )ENGINE = MYISAM;
    9.  
    Вот пример многопоточного скрипта:
    Код (PHP):
    1. <?php
    2.  
    3.  
    4. $DB['host'] = "localhost";
    5. $DB['user'] = "root";
    6. $DB['password'] = "";
    7. $DB['database'] = "test";
    8.  
    9.  
    10. $data = file("test_data.txt");
    11. $data_size = count($data);
    12. if($data_size < 1)
    13.     exit("No data to process.\n");
    14. $workers = [ ];
    15. $t_count = 50;
    16. $max_arr_size = 150;
    17. $flag = 1;
    18. $k = 0;
    19. $perc_diff = 1;
    20. $last_percent = 0;
    21. $s_time = microtime ( TRUE );
    22. while ( $flag === 1 ) {
    23.     
    24.     $c_w = count ( $workers );
    25.     
    26.     if ($c_w < $t_count) {
    27.             
    28.         for($i = $c_w; $i < $t_count - $c_w; $i ++) {
    29.             if ($k >= $data_size) {
    30.                 $flag = 0;
    31.                 break;
    32.             }
    33.                         
    34.             $array_size = 0;
    35.                         $new_array = array ();
    36.  
    37.                         while ( $array_size < $max_arr_size ) {
    38.                                 if ($k >= $data_size)
    39.                                         break;
    40.  
    41.                                 if ($array_size >= $max_arr_size)
    42.                                         break;
    43.  
    44.                                 $new_array [] = $data [$k];
    45.                                 $k ++;
    46.                                 $array_size ++;
    47.                         }
    48.                         //echo "--".$k."\n";                          
    49.             $workers [$i] = new test_thread ( $new_array, $DB );
    50.             // echo $data[$k]."\n";
    51.             $workers [$i]->start ();
    52.             //$k ++;
    53.         }
    54.     }
    55.     
    56.         //echo "gogog";
    57.     $c_w = count ( $workers );
    58.         
    59.     for($i = 0; $i < $c_w; $i ++) {
    60.         if ($workers [$i]->join ()) {
    61.             //echo "joining $i\n";
    62.             unset ( $workers [$i] );
    63.         }
    64.     }
    65.         
    66.         
    67.     $e_time = microtime ( TRUE );
    68.     $t = $e_time - $s_time;
    69.     $percent = ($k / $data_size) * 100;
    70.     if (($percent - $last_percent) >= $perc_diff || $percent == 100) {
    71.             
    72.             $last_percent = $percent;
    73.             echo "progress: " . $percent . " percent ($k of $data_size\n";
    74.             echo "time: " . $t . "\n";
    75.         }
    76.         
    77.     //sleep(1);
    78. }
    79.  
    80.  
    81. class test_thread extends Thread {
    82.     
    83.     private $my_str;
    84.     private $new_arr;
    85.     private $db_data;
    86.         
    87.     function __construct($new_arr, $db_data) {
    88.             
    89.         $this->my_arr = $new_arr;
    90.                 $this->db_data = $db_data;
    91.         
    92.     }
    93.     public function run() {
    94.         //print_r($this->my_arr);
    95.         //exit();
    96.         $DB = $this->db_data;
    97.         //print_r($DB);               
    98.         //exit();
    99.         $mysqli_obj = new mysqli($DB['host'], $DB['user'], $DB['password'], $DB['database'] );
    100.         if(mysqli_connect_errno())
    101.         {
    102.                 printf("MySQL connection error: %s", mysqli_connect_error());
    103.                 return;
    104.  
    105.         }
    106.  
    107.         $arr = $this->my_arr;
    108.  
    109.         foreach($arr as $str){
    110.             
    111.                     $str = trim($str);
    112.                     preg_match("/[_A-Za-z0-9-\.]+(\.[_A-Za-z0-9-]+)*@([A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[A-Za-z]{2,5}))/", $str, $matches);
    113.                     $mail = trim($matches[0]);
    114.                     $domain = trim(strtolower($matches[2]));                                    
    115.  
    116.                     $query = "INSERT INTO mail(mail, domain) values('".$mysqli_obj->real_escape_string($mail)."', '".$mysqli_obj->real_escape_string($domain)."')";
    117.                     //echo $query."\n";
    118.                     $result = $mysqli_obj->query($query);
    119.                     if($mysqli_obj->error)
    120.                     {
    121.                                     echo $mysqli_obj->error."\n";
    122.  
    123.                     }
    124.  
    125.         }
    126.  
    127.         return;
    128.  
    129.         }
    130. }
    131.  
    Для его нормальной работы нужен php7 x64 + pthreads 3.1.x (наверное любой v3 подойдет). Когда тестовый пример делал, то убрал из него чтение ГеоИП данных из бинарного файла и скорость получилась очень даже неплохой:

    Код (PHP):
    1. progress: 99 percent (1980000 of 2000000)
    2. time: 57.551290988922
    3. progress: 100 percent (2000000 of 2000000)
    4. time: 58.096322059631
    Может дело в том что у меня винт тормознутый и можно как-нибудь прочитать файл в память и уже оттуда с ним работать. Но там не простой текстовый файл а бинарний в каком-то своем формате и нужны специальные апи для работы с ним:

    Код (PHP):
    1. $gi = geoip_open("GeoIP/GeoIP.dat", GEOIP_STANDARD);
    2. $country = geoip_country_name_by_addr($gi, $ip); 
    Получается что именно такие операции тормозят всю работу. А простое чтение и запись в бд, как оказалось, весьма шустро происходит.
     
  16. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Проблема оказалась еще в том что скрипт пользует
    Код (PHP):
    1. $ip = gethostbyname($domain); 
    который работает весьма тормознуто. Интересно, как это обойти?
     
  17. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.600
    Симпатии:
    1.764
    А никак. Функция работает "тормознуто", поскольку лезет на внешний сервер.
     
  18. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Ясно. Ну, щито поделать, как говорится.
     
  19. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.559
    Симпатии:
    632
    Если домены повторяются, то нужно поднять локальный DNS и обращения к нему будут значительно быстрее
     
  20. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Да неплохая идея. Мне тут подсказали что можно в самом скрипте кэшировать домен=ип. Только это у меня пока не получается для многопоточного скрипта запилить.
     
  21. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.559
    Симпатии:
    632
    Скидывай данные в memcached =)
     
  22. freelsd

    freelsd Активный пользователь

    С нами с:
    12 апр 2015
    Сообщения:
    63
    Симпатии:
    0
    Протестил я с кэшированием средствами рнр. Наверное ваш вариант с кэширующим днс пока самое лучшее решение.