Поток UDP recvfrom использует слишком много ресурсов ЦП

Я пишу серверное приложение Visual C++ для Windows 7, которое должно получать дейтаграммы UDP со скоростью 3,6 МБ/с. У меня есть основной поток, в котором recvfrom() получает данные. Сокет является неблокирующим сокетом и имеет буфер приема 64 КБ. Если в сокете не было получено никаких данных, поток выполняет sleep(1).

Моя проблема в том, что поток использует почти 50% моего двухъядерного процессора, и я понятия не имею, как его уменьшить. Wireshark использует только 20%, поэтому моя главная цель — добиться такого же процента.

Есть ли у вас какие-либо идеи?


person Tom_K    schedule 18.01.2013    source источник
comment
Если вы чувствуете себя смелым, вы можете рассмотреть возможность использования Boost::async, но по сути это ответ @simonc об использовании select, заключенного в библиотеку классов.   -  person Caribou    schedule 18.01.2013


Ответы (4)


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

Сначала сделайте свой сокет неблокирующим:

u_long nonBlocking = 0;
WSAEventSelect(sock, NULL, 0);
ioctlsocket(sock, FIONBIO, &nonBlocking);

затем используйте WSAWaitForMultipleEvents ждать, пока не поступят данные или вы не захотите отменить recv:

int32_t MyRecv(THandle sock, WSAEVENT* recvCancelEvt,
               uint8_t* buffer, uint32_t bufferBytes)
{
    int32_t bytesReceived;
    WSAEVENT evt;
    DWORD ret;
    HANDLE handles[2];

    event = WSACreateEvent();
    if (NULL == evt) {
        return -1;
    }
    if (0 != WSAEventSelect(handle->iSocket, evt, FD_READ|FD_CLOSE)) {
        WSACloseEvent(evt);
        return -1;
    }

    bytesReceived = recv(sock, (char*)buffer, bufferBytes, 0);
    if (SOCKET_ERROR==received && WSAEWOULDBLOCK==WSAGetLastError()) {
        handles[0] = evt;
        handles[1] = *recvCancelEvt;
        ret = WSAWaitForMultipleEvents(2, handles, FALSE, INFINITE, FALSE);
        if (WAIT_OBJECT_0 == ret) {
            bytesReceived = recv(handle->iSocket, (char*)buffer, bufferBytes, 0);
        }
    }
    WSACloseEvent(evt);
    return bytesReceived;
}

Клиентский код будет вызывать WSASetEvent на recvCancelEvt, если он хочет отменить получение.

person simonc    schedule 18.01.2013
comment
Спасибо за ответ. Ваш пример очень поучителен. В любом случае, я должен был понять, что неэффективен не мой простой механизм приема, а то, как я обрабатывал данные. - person Tom_K; 22.01.2013

Хотя решения, основанные на выборе или блокировке сокетов, являются правильным подходом, причина, по которой вы используете одно ядро ​​​​на 100%, связана с поведением Sleep: -

Посмотрите документы для спящего режима WinAPI. ():

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

Таким образом, если вы проводите опрос, вам нужно либо использовать гораздо большее время ожидания (возможно, 20 мс, что обычно немного больше, чем частота тактов Windows), либо использовать более точный мультимедийный таймер.

person Roddy    schedule 18.01.2013

Я бы рекомендовал использовать boost::asio::io_service. Мы получаем около 200 МБ/с многоадресного трафика UDP при максимальном использовании современного процессора. Это включает в себя полный протокол надежности и отправку данных в приложение. Узким местом в профилировании является обработка, а не получение boost::asio. Код здесь

person eile    schedule 18.01.2013
comment
Библиотеки буста еще не надоели, но основная проблема заключалась в обработке, а не в получении. - person Tom_K; 22.01.2013

Похоже, что в большинстве случаев ваш вызов recvfrom не возвращает данные. Спать 1 мс не так уж и много. Вы должны подумать об увеличении времени сна (дешевое, но не лучшее решение) или, что лучше, подумать об использовании подхода, управляемого событиями. Используйте select() или Windows API для блокировки до тех пор, пока сокет не получит сигнал или не произойдет какое-либо другое интересующее вас событие, а затем вызовите recvfrom. Для этого вам может понадобиться изменить основной цикл вашей программы.

person Werner Henze    schedule 18.01.2013