Медленно работает многопоточная загрузка multiCurl

Discussion in 'PHP' started by Tisar, 19 Dec 2010.

  1. Tisar

    Tisar New Member

    Joined:
    12 Sep 2009
    Messages:
    18
    Likes Received:
    0
    Reputations:
    0
    Здравствуйте.
    Ситуация следующая.
    Имеется скрипт для многопоточной загрузки с использованием multiCurl. Так вот по моему мнению он работает как то очень медленно на localhost`е(Denver) при работе в 50 потоков 500 файлов размером 13 байт загружались 177 секунд. Встречал сообщения что multiCurl запускали работать и на 1000 потоков и всё работало.

    В производительность компьютера скрипт точно не упирается (Core 2 Duo E8500, ОЗУ 4 Гб) повышения потребления памяти не заметил, загрузка ЦП 5-10%.

    PHP:
    function multiCurl($data//Многопоточная загрузка данных
    {
        
    $time1=time();
        
    $curls = array();
        
    // Массив дескрипторов. Библиотека создает много экземпляров своего
        // механизма, но работать они будут параллельно

        
    $result = array();
        
    //массив с результатами запрошенных страниц которые наша функция вернет.

        
    $mh curl_multi_init();
        
    // Дескриптор мульти потока. Тоесть эта штука отвечает за то, чтобы много
        // запросов шли параллельно.

        
    $uagent "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8";

        
    $header[] = "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
        
    $header[] = "Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3";
        
    $header[] = "Accept_charset: windows-1251,utf-8;q=0.7,*;q=0.7";
        
    $header[] = "Accept_encoding: gzip,deflate";
        
    $header[] = "Keep_alive: 300";
        
    $header[] = "Connection: keep-alive";

        foreach (
    $data as $IdRequest => $DataRequest//Создание потока для закачки
        
    {
            
    $curls[$IdRequest] = curl_init(); // Для каждого url создаем отдельный curl механизм чтоб посылал запрос)

            
    curl_setopt($curls[$IdRequest], CURLOPT_URL$DataRequest['url']);
            
    curl_setopt($curls[$IdRequest], CURLOPT_RETURNTRANSFER1);

            
    curl_setopt($curls[$IdRequest], CURLOPT_HEADER0);
        
            
    curl_setopt($curls[$IdRequest], CURLOPT_HTTPHEADER $header);
            
    curl_setopt($curls[$IdRequest], CURLOPT_RETURNTRANSFER1);   // возвращает веб-страницу
            
    curl_setopt($curls[$IdRequest], CURLOPT_ENCODING"");        // обрабатывает все кодировки
            
    curl_setopt($curls[$IdRequest], CURLOPT_USERAGENT$uagent);  // useragent
            
    curl_setopt($curls[$IdRequest], CURLOPT_CONNECTTIMEOUT25); // таймаут соединения
            
    curl_setopt($curls[$IdRequest], CURLOPT_TIMEOUT25);        // таймаут ответа

            
    curl_setopt($curls[$IdRequest], CURLOPT_FOLLOWLOCATION0);   // переходит по редиректам
            //curl_setopt($curls[$IdRequest], CURLOPT_MAXREDIRS, 1);        // останавливаться после 1-ого редиректа

            // добавляем текущий механизм к числу работающих параллельно
            
    curl_multi_add_handle($mh$curls[$IdRequest]);
        }

        
    // число работающих процессов.
        
    $running null;
        
    $time2=time();
        
    // curl_mult_exec запишет в переменную running количество еще не завершившихся
        // процессов. Пока они есть - продолжаем выполнять запросы.
        
    do
        {
            
    curl_multi_exec($mh$running);
            
    usleep(50);
        } while(
    $running 0);

        
    // Собираем из всех созданных механизмов результаты, а сами механизмы удаляем
        
    foreach($curls as $id => $c)
        {
            
    $result[$id] = curl_multi_getcontent($c);
            
    curl_multi_remove_handle($mh$c);
        }
        
    $time3=time()-$time2;
        
    // Освобождаем память от механизма мультипотоков
        
    curl_multi_close($mh);

        echo 
    'Функция multiCurl завершена. Время работы всей функции/получение загружаемых данных (do while)'.(time()-$time1).'/'.$time3.'. Колличество загрузок(потоков):'.$x.'<br>';
        
    // возвращаем данные собранные из всех потоков.
        
    ksort($result);
        return 
    $result;
    }
    1) файл 1 - создание файлов с случайным содержимым и файла для контроля корректности загрузку
    PHP:
    /**
     * Функция для создания файла и записи в него. Варианты работы: Дозапись файла, Перезапись файла
     *
     * @param string $Content содержимое файла
     * @param string $PathFiles Путь к файлу
     * @param Boolean $log Выбор варинта работы с файлом: Дозапись файла, Перезапись файла
     */
    function save_to_files($Content,$PathFiles,$log)
    {
        if (
    $log==True)
        {
            
    $handle fopen($PathFiles"a"); //Дозапись файла
        
    }
        else {
    $handle fopen($PathFiles"wb");} //Перезапись файла
        
    if (fwrite($handle$Content) === FALSE)
        {
            echo 
    "<br/>Не могу произвести запись в файл";
            exit;
        }
        if (!
    $handle) echo 'Ошибка открытия файла';
        
    fclose($handle);
    }

    $stream=50;
    $CyclesTesting=10;
    if(!
    is_dir(".\TestMultiCurl")) mkdir(".\TestMultiCurl"0700);
    for (
    $i=0;$i<($stream*$CyclesTesting);$i++)
    {
        
    $ContentFiles[$i]=$i.'_'.mt_rand();
        
    save_to_files($ContentFiles[$i],'.\TestMultiCurl\test'.$i.'.txt',False);
    }
    //Сохраниени сгенерированного содержимого всех файлов
    save_to_files(implode("\r\n"$ContentFiles),'.\TestMultiCurl\CheckFiles.txt',False);
    2) файл 2 - загрузка файлов и проверка корректности загрузки
    PHP:
    //вставить сюда функцию multiCurl
    <?php
    $Time
    =time();
    $stream=50;
    $CyclesTesting=10;
    $UrlPath='http://testsait1/TestMultiCurl';
    //$UrlPath='http://www.marketing-trade.ru/TestMultiCurl';

    //Создание задания для загрузки файла с данными для провеки загрузок
    $Request[0] =Array('url' => $UrlPath.'/CheckFiles.txt''post' => '''cookie' => '''refer' => '''proxy' => '');
    $CheckFiles=multiCurl($Request,0);
    unset(
    $Request);
    echo 
    '<br/>Затрачено времени на загрузку контрольного файла: '.(time()-$Time).'<br/>';
    //деление загруженного файла на строки и помещение их в массив
    $ArrayCheckFiles=explode("\r\n"$CheckFiles[0]);
    //Загрухка файлов
    $i=0;
    unset(
    $CheckFiles);
    $CheckFiles=array();
    $i1=0;
    while(
    $i<=($stream*$CyclesTesting))
    {
        
    $i1++;
        
    $Request[$i] =Array('url' => $UrlPath.'/test'.$i.'.txt''post' => '''cookie' => '''refer' => '''proxy' => '');
        if (
    $i1==$stream)
        {
            
    //var_dump($Request);
            
    $CheckFiles0=multiCurl($Request,0);
            
    $CheckFiles=array_merge($CheckFiles$CheckFiles0);
            unset(
    $CheckFiles0);
            unset(
    $Request);
            
    $i1=0;
        }
        
    $i++;
    }
    if (
    count($Request)>0)
    {
        
    $CheckFiles0=multiCurl($Request,0);
        
    $CheckFiles=array_merge($CheckFiles$CheckFiles0);
        unset(
    $CheckFiles0);
        unset(
    $Request);
    }
    //Провекра корректности загруженных файлов
    $ErrorCheck=0;
    foreach(
    $ArrayCheckFiles as $id => $Content)
    {
        if (
    $Content!=$CheckFiles[$id])
        {
            
    $ErrorCheck++;
            echo 
    '<br/>ERROR:<br/>'.$Content.'<br/>'.$CheckFiles[$id];
        }
    }
    echo 
    '<br/><br/>Всего файлов/Ошибок: '.count($ArrayCheckFiles).'/'.$ErrorCheck;
    echo 
    '<br/><br/>Затрачено времени: '.(time()-$Time);
    ?>
    3) добавит в файл 2 функцию multiCurl
    4) указать адрес где лежать загружаемые файлы $UrlPath

    Осталась надежда только на гуру с форума, подскажите пожалуйста почему происходит такая хрень, что я сделал неправильно? Протестируйте этот скрипт кто нибудь у себя, хотелось бы сравнить результаты.
     
    #1 Tisar, 19 Dec 2010
    Last edited: 19 Dec 2010
  2. b3

    b3 Moderator

    Joined:
    5 Dec 2004
    Messages:
    2,041
    Likes Received:
    933
    Reputations:
    199
    Приходилось работать с мультикурлом, тоже обратил внимание на то что скорость вырастает не значительно, так же заметил что при работе свыше 10 потоков, не стабильно парсится контент.
    мб потому что это не многопоточность а эмуляция её.
     
    _________________________
  3. Tisar

    Tisar New Member

    Joined:
    12 Sep 2009
    Messages:
    18
    Likes Received:
    0
    Reputations:
    0
    как было выяснено периодически происходить очень долгая загрузка файла (цикл do while):
    почему хз
     
  4. Gifts

    Gifts Green member

    Joined:
    25 Apr 2008
    Messages:
    2,494
    Likes Received:
    807
    Reputations:
    614
    Tisar Не стоит забывать несколько вещей:

    Курл открывает новое соединение на каждый запрос, то есть тратится дополнительное время на открытие соединения.

    Не в последнюю очередь на ваш тест влияют настройки апача - а конкретно количество процессов/потоков обрабатывающих входящие соединения

    При отправке HTTP запроса - генерируется множество заголовков, то есть вы получаете не 13 байт, а много больше

    Я бы посоветовал вам запустить например ApacheBench с тем же количеством потоков и запросов и посмотреть на реакцию сервера. Если разница с вашим скриптом будет существенна - ковыряйтесь в нем, если нет - настраивайте сервер.
     
    _________________________
    1 person likes this.
  5. Tisar

    Tisar New Member

    Joined:
    12 Sep 2009
    Messages:
    18
    Likes Received:
    0
    Reputations:
    0
    А что смотреть в этом отчёте?
    Запуск(1) файла с выполняющего загрузку в 50 потоков

    запуск(500) файла с содержимым "echo 'test';":
    Тест выполнялся на localhost`е Denver
     
  6. Gifts

    Gifts Green member

    Joined:
    25 Apr 2008
    Messages:
    2,494
    Likes Received:
    807
    Reputations:
    614
    Tisar в первом случае вы делаете двойную работу. С помощью ab.exe вы должны скачивать сами файлы, или же один файл но 500 раз. А получается что вы скачиваете скрипт, скачивающий файлы.

    По второму тесту - скорость 32 кбайта - какая то слишком уж печальная, попробуйте увеличить количество потоков у апача

    И дополнительно сделайте все же тест на одновременные соединения: ab -n 500 -c 50
     
    _________________________
  7. Deathdreams

    Deathdreams Elder - Старейшина

    Joined:
    8 Nov 2008
    Messages:
    342
    Likes Received:
    116
    Reputations:
    5
    Скорость зависит и от среды, в которой запущен Apache.
    Разные факторы влияют на скорость.
     
  8. Tisar

    Tisar New Member

    Joined:
    12 Sep 2009
    Messages:
    18
    Likes Received:
    0
    Reputations:
    0
    Да действительно дело оказалось в количестве потоков Апача (переменная ThreadsPerChild )

    Тест при настройках ThreadsPerChild=60
    Тест при настройках ThreadsPerChild=25 (по умолчанию)