неблокирующая производительность записи сокетов в c ++ - PullRequest
2 голосов
/ 20 февраля 2012

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

Итак, я создаю сокет:

socket(AF_INET, SOCK_STREAM, 0);
int one = 1;
setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)));
current = fcntl(socket, F_GETFL);
fcntl(socket, F_SETFL, O_NONBLOCK | current);

Мои сообщения всегда имеют размер около 200 байт. Код, который отправляет сообщение:

uint64_t start (nanotimestamp());
unsigned char * buf;
... 
//build a message
//few calls to snprintf
//buffer is preallocated
...
write(socket, buf, size);
uint64_t end (nanotimestamp());

performance = end - start;

uint64_t nanotimestamp()
{
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
return now.tv_sec * 1e9 + now.tv_nsec;
}

Код выполняется на 64-битной Redhat 6. Скомпилировано с GCC 4.4 Время варьируется от 20 до 80 мкс, в редких случаях> 100 мкс.

Так что, если вызов для записи не блокирует, почему я вижу такую ​​разницу?

Ответы [ 5 ]

2 голосов
/ 24 февраля 2012

Вы должны рассмотреть возможность использования CLOCK_MONOTONIC для этого измерения - оно имеет значительно меньшие накладные расходы, чем получение CLOCK_REALTIME.Для измерения производительности (нам нужна точность в наносекунды) я использую счетчик RDTSC:

С GCC 4.4+ (в этом нет уверенности на 100%, 4.6.1 это точно реализует) в системе Intel, которую вы можете использовать:

#include <x86intrin.h>
uint64_t clock_count = __rdtsc();

или, если нет:

extern "C" {
    __inline__ uint64_t rdtsc()
    {
        uint32_t lo, hi;
        __asm__ __volatile__ (
            "xorl %%eax,%%eax \n        cpuid"
            ::: "%rax", "%rbx", "%rcx", "%rdx");
        __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
        return (uint64_t)hi << 32 | lo;
    }
}

, то деление дельты тактовых импульсов на частоту вашего процессора на число герц частоты процессора даст вам очень точное измерение на долю отстоимость clock_gettime()

Редактировать:

И чтобы ответить на актуальный вопрос сейчас:)

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

Я думаю, что проблема в сочетании snprintf () и отсутствия кэша.Функции форматирования имеют очень низкую производительность, и, поскольку вы каждый раз перестраиваете данные, существует вероятность того, что время от времени вы пропускаете кэш , что должно ответить на вопрос об изменчивости.

1 голос
/ 24 февраля 2012

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

Однако есть еще одна вещь, которую следует учитывать.В зависимости от различных факторов, ваш неблокирующий вызов write () может использовать очень разные пути кода.Например, может потребоваться выделить дополнительные буферы, что занимает время, а может и не понадобиться.Или может быть решено, что данные должны быть отправлены сразу же и отправлены «прямо в металл» (вызов драйвера для передачи данных на сетевой интерфейс для передачи).

Распределение буферов требует времени, доставкаданные к сетевому интерфейсу тем более.

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

0 голосов
/ 20 февраля 2012
  1. было бы предпочтительнее окружить вызов write () только для того, чтобы узнать, сколько времени требуется для выполнения своей "неблокирующей" обязанности.
  2. Поток, выполняющий этот код, может в любой момент быть выгружен ядром. Который будет планировать другой поток / процесс на текущем ядре. Это перепланирует ваш процесс позже. 20-80us может быть временем выполнения другого процесса. (Хотя я больше ожидал 20-50 миллисекунд, но это зависит от вашего ядра и его конфигурации).
0 голосов
/ 20 февраля 2012

Было бы интересно отделить вызовы write(), которые терпят неудачу с EAGAIN, от вызовов, которые фактически передают данные.Это может объяснить большую часть несоответствия.

Также мне интересно, действительно ли TCP_NODELAY помогает вам в этом сценарии.Возможно, это стоит того.

0 голосов
/ 20 февраля 2012

Вы не можете измерить только 1 запись, так как процесс может быть приостановлен во время вызова записи (это может составлять> 100 мкс).Кроме того, выполнение системного вызова может привести к некоторой дисперсии.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...