IPC Первые вызовы send() и recv() в C++ TCP занимают больше времени

Я использую IPC (межпроцессное взаимодействие) для отправки данных между двумя программами на C++. Для IPC я использую базовый код TCP/IP Winsock2.

В основном идея состоит в том, что я отправляю кадры 3D-данных между двумя приложениями, одно обрабатывает данные с помощью GPU/CUDA, а другое отображает их с помощью сторонней библиотеки (PCL). Мои приложения очень богаты данными (например, 30 кадров в секунду, 4 МБ на кадр), но, насколько я знаю, это не должно быть проблемой для IPC.

Поэтому, когда обработка каждого кадра на графическом процессоре завершается, я конвертирую данные кадров (координаты X, Y, Z типа float; R, G, B типа uint8_t каждый) в байты и отправляю их один за другим.

Делая это, я заметил нечто странное. В моем коде у меня есть 9 команд send() одна за другой.

  • 1ste : Отправка одного символа, который действует как имя кадра.
  • 2de : одно целое число с количеством поступающих 3D-точек.
  • 3-5th: значения RGB
  • 6-8th: значения XYZ
  • 9-е: Конец проверки связи, чтобы закрыть приложение просмотра.

Весь этот процесс занимает ~ 30 мс. Что мне показалось странным, так это то, куда уходила большая часть времени. После определения времени каждого события я получил следующее:

  • 1-й этап: 20 мс (1 байт данных)
  • 2de : ‹1 мс (4 байта данных)
  • 3-5: 2 мс (921600 байт данных)
  • 6-8: 3 мс (3686400 байт данных)
  • 9-й: 1 мс (1 байт данных)

Почему для первой команды отправки требуется так много времени, даже если это всего 1 байт данных, а затем остальные данные завершаются в рекордно короткие сроки. Между каждым запуском этого цикла существует задержка около 20 мс, ожидающая завершения кода графического процессора. Переходит ли TCP-соединение в спящее состояние, и если да, то могу ли я его как-то отключить.

Код сокета TCP:

SOCKET Create_Server_Socket(PCSTR IP, PCSTR port)
{
struct addrinfo *result = NULL, *ptr = NULL, hints;
int iResult;

ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;

// Resolve the local address and port to be used by the server
iResult = getaddrinfo(NULL, port, &hints, &result);
if (iResult != 0) {
    printf("getaddrinfo failed: %d\n", iResult);
    WSACleanup();
    return 1;
}

SOCKET ListenSocket = INVALID_SOCKET;

ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);

if (ListenSocket == INVALID_SOCKET) {
    printf("Error at socket(): %ld\n", WSAGetLastError());
    freeaddrinfo(result);
    WSACleanup();
    return 1;
}

iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
    printf("bind failed with error: %d\n", WSAGetLastError());
    freeaddrinfo(result);
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

freeaddrinfo(result);

if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
    printf("Listen failed with error: %ld\n", WSAGetLastError());
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

SOCKET ClientSocket;

ClientSocket = INVALID_SOCKET;

// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
    printf("accept failed: %d\n", WSAGetLastError());
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

return (ClientSocket);

}

Рассматриваемый код:

iResult = send(ConnectSocket, (char*)&name, sizeof(char), 0);  //Takes >20ms to complete
iResult = send(ConnectSocket, (char*)&points, 4, 0);
iResult = send(ConnectSocket, (char*)Red_t, sizeof(uint8_t) * depth_width *depth_height, 0);
iResult = send(ConnectSocket, (char*)Green_t, sizeof(uint8_t) * depth_width *depth_height, 0);
iResult = send(ConnectSocket, (char*)Blue_t, sizeof(uint8_t) * depth_width *depth_height, 0);
iResult = send(ConnectSocket, (char*)z_t, sizeof(uint16_t) * depth_width *depth_height, 0);
iResult = send(ConnectSocket, (char*)x_t, sizeof(float) * depth_width *depth_height, 0)
iResult = send(ConnectSocket, (char*)y_t, sizeof(float) * depth_width *depth_height, 0);
iResult = send(ConnectSocket, "a", sizeof(char), 0);

Я работаю над Windows 10 (т.е. Winsock2 lib).

Заранее спасибо!


person Hugo Coppejans    schedule 23.07.2017    source источник
comment
Если вам нужен быстрый IPC в Windows, зачем использовать сокеты? Особенно TCP, который добавляет довольно много накладных расходов (даже при локальном использовании). Должны быть какие-то общие структуры в памяти, которые вы могли бы использовать (возможно, просто общая память с общим мьютексом для защиты и пара каких-то общих сигналов, чтобы сказать, когда пакет с любой стороны готов) .   -  person Some programmer dude    schedule 23.07.2017
comment
Я изучил Named Pipes, но многие люди сказали, что их производительность примерно одинакова. Насколько я знаю, если вы используете TCP локально, это позволяет избежать большей части накладных расходов.   -  person Hugo Coppejans    schedule 23.07.2017
comment
Есть ли причина для использования так может отправлять звонки? В отличие от сериализации данных (например, в структуре), а затем использовать один вызов отправки?   -  person Jonas    schedule 23.07.2017


Ответы (1)


Вы, вероятно, страдаете от «алгоритма Нэгла». вики/Nagle%27s_algorithm

Короче говоря, в TCP/IP встроена задержка с целью сбора достаточного количества данных для отправки пакета до того, как будет отправлен первый пакет. Существует опция TCP_NODELAY, которую вы можете использовать при открытии сокета, чтобы отключить это, если для вас это проблема.

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

person Mike Nakis    schedule 23.07.2017
comment
Любые мысли о том, как TCP_NODELAY реагирует на такие вызовы для отправки? - person Jonas; 23.07.2017
comment
Насколько я знаю, он просто не будет задерживать ваши первые несколько крошечных пакетов, пока не накопится достаточно данных, чтобы их можно было отправлять. Таким образом, ваш TTFB (время до первого байта) будет сокращен. Однако время до последнего байта не будет затронуто, поэтому, если вы всегда будете отправлять все эти данные, и если другой стороне нечего делать, пока все данные не поступят, то это не будет иметь большого значения. . На самом деле, это может привести к немного худшей производительности, потому что будет обмениваться большим количеством пакетов, и каждый пакет будет иметь небольшие накладные расходы. - person Mike Nakis; 23.07.2017