Низкая пропускная способность UDP по сравнению с производительностью iperf UDP

При измерении пропускной способности UDP между ПК с Windows и устройством на базе Zynq с помощью инструмента iperf2 я получаю около 950 Мбит / с по выделенному каналу Ethernet 1 Гбит. Однако при использовании моего собственного приложения UDP на ПК я получаю только около 50 Мбит / с, что значительно ниже пропускной способности, измеренной iperf. Конечно, в моем приложении UDP у меня нет никакой обработки, только цикл while, в котором я вызываю функцию sendto, с пакетами UDP размером 1470 байт. Приложение на устройстве Zynq предоставлено XAPP1026, поэтому оно не мое. Я смотрю на код iperf, пытаясь понять, что они делают по-другому, но в основном я не могу найти какие-либо параметры сокета или udp или что-то подобное, что они делают, чтобы максимизировать пропускную способность UDP.

Вот код основной функции (определение MAXUDP равно 1470):

int main(int argc, char** argv) 
{
int sockfd;
struct sockaddr_in servaddr;
char sendline[MAXUDP];
int i;
int j;
const int tr_size = ( 200 * MB );
const int npackets = ( tr_size / MAXUDP );
const int neval = 2;
DWORD start;
DWORD end;
int optval;

WSADATA wsaData;
if(WSAStartup(MAKEWORD(2, 1), &wsaData) != 0 )
{
    printf( "Err: %d\n", WSAGetLastError() );
    exit(1);
}

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = inet_addr("172.16.0.215");

sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
Connect(sockfd, (const SA*) &servaddr, sizeof(servaddr));

optval = 208*KB;
Setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char*) &optval, sizeof optval);

prep_data(sendline, MAXUDP);

for ( i = 1; i <= neval; i++ )
{
    start = GetTickCount();
    for ( j = 0; j < npackets/neval; j++ )
        sendto(sockfd, sendline, MAXUDP, 0, NULL, NULL);
    end = GetTickCount() - start;

    printf("Time elapsed: %d sec.\n", end/1000);
    printf("Throughput: %d.%3d MB/s\n", (tr_size/neval)/end/1000, (tr_size/neval)/end - (tr_size/neval)/end/1000);
}
return 0;
}

Итак, мой главный вопрос заключается в том, как максимизировать пропускную способность UDP так же, как это делает iperf?

ОБНОВЛЕНИЕ: я перешел на ПК с Ubuntu. Результаты разные, но все же происходят некоторые случайные вещи. Первое, что я делаю, это устанавливаю IP-адреса для eth0 (ifconfig eth0 172.16.0.200 netmask 255.255.255.0) и адрес шлюза (route add default gw 172.16.0.1). Когда я запускаю iperf с iperf -c 172.16.0.215 -i 5 -t 25 -u -b 1000m), я получаю около 800 Мбит/сек. Однако, после нескольких запусков iperf таким же образом, внезапно я начинаю получать только около 15 Мбит / с или даже намного меньше. Я понял, что мне нужно снова установить IP, сетевую маску и адреса шлюза, чтобы получить 800 Мбит/сек. Кроме того, мое приложение UDP ведет себя точно так же. Я измеряю 957 Мбит/с (с MAXUDP, установленным на 1470) после того, как я выполнил команды для установки IP-адресов. Но после нескольких итераций он замедляется примерно до 11 Мбит/с. Затем я снова устанавливаю IP-адреса, и поведение повторяется. Итак, как сказал Карием в своем ответе, проблема не в самом коде, а в некоторых ОС, связанных с конфигурацией netif. Однако я должен запустить свое UDP-приложение в Windows, поэтому мне нужно выяснить, что там происходит. Если у вас есть какие-либо идеи о том, что может происходить в Windows, пожалуйста, дайте мне сейчас.


person Irie    schedule 27.04.2017    source источник
comment
Опубликуйте свой код, пожалуйста. Это поможет.   -  person Kariem    schedule 27.04.2017
comment
Вы говорите, что отправляете дейтаграммы UDP с 1470-байтовой полезной нагрузкой или что ваши пакеты имеют общий размер 1470 байт? (Примечание: это пример вопроса, который нам не нужно было бы задавать, если бы вы предоставили минимальный воспроизводимый пример, как мы обычно ожидаем.)   -  person John Bollinger    schedule 27.04.2017
comment
Я добавил код, так что теперь все будет более понятно. Также я только что выяснил, что когда я ставлю MAXUDP не 1470, а скорее 14700 (ну, я просто экспериментирую), я получаю до 550 Мбит/с. Неразберихи тоже становится больше.   -  person Irie    schedule 27.04.2017
comment
@Irie: проверьте возвращаемое значение от sendto()! Вот как вы можете выяснить, сколько байтов было фактически отправлено, а не отброшено. send() не всегда отправляет все байты, которые вы просили отправить, поэтому вы, вероятно, неправильно измеряете, когда устанавливаете большие размеры буфера.   -  person John Zwinck    schedule 27.04.2017
comment
Верно, @JohnZwinck, но если бы на самом деле было отправлено меньше данных, чем предполагает OP, то он получил бы завышенную оценку пропускной способности. Это не объясняет его наблюдения; вместо этого повышается вероятность того, что реальная производительность может быть даже хуже, чем он думает.   -  person John Bollinger    schedule 27.04.2017
comment
Хотя я предполагаю, что, возможно, это объясняет очевидное увеличение пропускной способности, когда MAXUDP увеличивается до 14700.   -  person John Bollinger    schedule 27.04.2017
comment
Это UDP, поэтому он либо отправляет все байты, запрошенные в дейтаграмме, либо ничего?   -  person ThingyWotsit    schedule 27.04.2017
comment
Я добавил часть кода, которая проверяет возвращаемое значение из sendto() (никогда не возвращает значение меньше MAXUDP). В общем, результаты одинаковые.   -  person Irie    schedule 27.04.2017
comment
Очевидно, что большие значения MAXUDP уменьшают общее количество вызовов sendto(). Если sendto() всегда сообщает об успешной отправке MAXUDP байтов для каждого проверенного до сих пор значения MAXUDP, то стоит увеличить MAXUDP еще больше, чтобы уменьшить количество вызовов, потому что системные вызовы сравнительно дороги. Абсолютный максимальный размер полезной нагрузки, поддерживаемый протоколом UDP, составляет 65 507 байт.   -  person John Bollinger    schedule 27.04.2017
comment
Это имеет смысл. Ставлю MAXUDP на 65507 и получаю скорость до 695 Мбит/с. С другой стороны, я заметил, что iperf также вызывает sendto() с номером 1470 и получает еще более высокие результаты.   -  person Irie    schedule 27.04.2017
comment
Если вы также не меняете MTU на своем интерфейсе, маловероятно, что вы действительно отправляете пакеты размером 14000 байт. Используйте wireshark, чтобы увидеть пробелы в вашем протоколе.   -  person stark    schedule 27.04.2017
comment
Я использую Wireshark, и он не отправляет пакеты размером 14000 байт. Он делит (фрагментирует) эти пакеты на множество IP-фрагментов.   -  person Irie    schedule 27.04.2017
comment
Да — дейтаграммы UDP могут быть фрагментированы. Для ИП это нормально.   -  person ThingyWotsit    schedule 27.04.2017


Ответы (2)


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

изменять

printf("Throughput: %d.%3d MB/s\n", (tr_size/neval)/end/1000, (tr_size/neval)/end - (tr_size/neval)/end/1000);

к этому

printf("Throughput: %d.%3d MB/s\n", ((tr_size/neval)/end/1000)*8, (tr_size/neval)/end - ((tr_size/neval)/end/1000)*8);

Я запустил версию вашего кода на своем компьютере и получил пропускную способность 1 ГБ. Вот

#include <netinet/in.h>
#include <string.h>
#include <sys/time.h>
#include <cstdio>
#include <arpa/inet.h>
#include <fcntl.h>

#define MAXUDP 1470
#define SERV_PORT 5001
static inline long int getCurTimeInMs()
{
    struct timeval tp;
    gettimeofday(&tp, NULL);
    return tp.tv_sec * 1000 + tp.tv_usec / 1000;

}


int main(int argc, char** argv)
{
    int sockfd;
    struct sockaddr_in servaddr;
    char sendline[MAXUDP];
    int i;
    int j;
    const int tr_size = ( 10000 * 1024*1024 );
    const int npackets = ( tr_size / MAXUDP );
    const int neval = 2;

    int optval;

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);

        servaddr.sin_addr.s_addr = inet_addr("10.0.1.2");

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);


    connect(sockfd, (const sockaddr*) &servaddr, sizeof(servaddr));


    optval = 208*1024;
    setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char*) &optval, sizeof optval);
    long int start = 0, end = 0;
    for ( i = 1; i <= neval; i++ )
    {
        start = getCurTimeInMs();
        for ( j = 0; j < npackets/neval; j++ )
            sendto(sockfd, sendline, MAXUDP, 0, NULL, NULL);
        end = getCurTimeInMs() - start;

        printf("Time elapsed: %d sec.\n", end/1000);
        printf("Throughput: %d.%3d MB/s\n", (tr_size/neval)/end/1000 * 8, (tr_size/neval)/end - (tr_size/neval)/end/1000);

    }
    return 0;
}
person Kariem    schedule 27.04.2017
comment
Я бы не сказал, что там ошибка. Например, когда я пересылаю всего 100 МБ, я получаю время, прошедшее около 15 секунд, а пропускная способность составляет около 6,6 МБ/с, что вполне логично. Более того, глядя на статистику в Wireshark, мне нужно около 16 секунд для передачи 71250 пакетов UDP, каждый размером 1514 байт (71250 * 1514 = 107872500 (~100 МБ)). 71250*1514/16 — это около 6,7 МБ/с. - person Irie; 27.04.2017
comment
Да, цифры верны, если вы рассчитываете все в байтах в секунду, но это не то, что делает iperf. iperf вычисляет пропускную способность в битах в секунду, поэтому она будет в 8 раз больше расчетной пропускной способности. MBPS означает мегабит в секунду. - person Kariem; 27.04.2017
comment
Да, значит, я получаю в этом примере 6,6 * 1024 * 1024 * 8 Мбит/с, что составляет 55 Мбит/с. Это то, что я сказал в исходном посте. Этот результат далек от 950 Мбит/с, которые измеряет iperf. - person Irie; 27.04.2017
comment
Я пошел по вашему коду, поэтому, если вы вручную умножаете на 8 в конце, чтобы получить пропускную способность в битах в секунду, то это достаточно справедливо. Как я уже говорил в ответе, я протестировал ваш код и получил 1 Гбит/с, что соответствует пропускной способности моего кабеля Ethernet, поэтому ясно, что ваш код не проблематичен. Единственным другим объяснением может быть то, что ваш код по какой-то причине выполняется недостаточно часто. Попробуйте запустить его с более высоким приоритетом и попробуйте настроить сокет на неблокирующий. - person Kariem; 27.04.2017
comment
Да, я делал это вручную. Спасибо, что попробовали код! Я предполагаю, что вы измеряли пропускную способность между двумя ПК или другая сторона работала на каком-то другом устройстве? Я попытаюсь установить неблокирующий сокет, но я не знаю, как заставить его работать с более высоким приоритетом. Если у вас есть какие-либо ссылки на это, я был бы признателен, если бы вы могли поделиться. - person Irie; 27.04.2017
comment
Да, тестировался между настольным компьютером под управлением Ubuntu и взаимодействием с планшетом под управлением Android. Вы можете изменить приоритет в Linux, используя renice -n -20 -p ‹id процесса› - person Kariem; 27.04.2017

Почему вы определили MAXUDP 1470? Попробуйте установить его на 65535, измерьте еще раз и сообщите здесь.

Не следует путать размер кадра Ethernet (1500 байт) с размером дейтаграммы UDP. Разные вещи. Пусть стек IP выполняет необходимую фрагментацию вместо вашего приложения. Это может быть более эффективно.

person Sokre    schedule 28.04.2017
comment
Я тоже так сделал. В Windows в этом случае я получаю до 695 Мбит / с. Однако iperf в Windows отправляет дейтаграммы размером 1470 байт и получает более 900 Мбит/с. Я не знаю, как это объяснить. - person Irie; 28.04.2017
comment
При запуске моего приложения в Ubuntu я получаю 955 Мбит/с, даже если MAXUDP установлено на 1470. - person Irie; 28.04.2017
comment
Еще кое-что. Часть вашего кода, где вы измеряете прошедшее время (цикл for с sendto() в нем), измеряет только то, насколько быстро ваше приложение передает данные в стек сокета/IP. Нет обратной связи с другой стороны при использовании UDP. С таким же успехом можно отсоединить кабель. - person Sokre; 28.04.2017
comment
Кроме того, я вижу, что вы приводите optval к ​​(const char *) в вызове setsockopt и объявляете его как int. Эта страница говорит ожидаемый тип данных — DWORD. - person Sokre; 28.04.2017
comment
Да, вы правы, даже когда я отключаю кабель, я получаю те же результаты. Однако на стороне сервера (устройство Zynq) я измеряю количество полученных данных, так что это мой отзыв с другой стороны. Эта строка кода с setsockopt ничего не меняет. Тем не менее, я также ставлю getockopt после этой строки и получаю ожидаемое значение. - person Irie; 28.04.2017