ruta resueltos pert operaciones metodo investigacion ejercicios ejemplos diagrama critica cpm caracteristicas c linux sockets network-programming netbsd

resueltos - ¿Por qué cambia la latencia de la red medida si uso un reposo?



pert diagrama (5)

Estoy tratando de determinar el tiempo que le lleva a una máquina recibir un paquete, procesarlo y devolver una respuesta.

Esta máquina, que llamaré ''servidor'', ejecuta un programa muy simple, que recibe un paquete ( recv(2) ) en un búfer, copia el contenido recibido ( memcpy(3) ) en otro búfer y lo envía de vuelta ( send(2) ). El servidor ejecuta NetBSD 5.1.2.

Mi cliente mide el tiempo de ida y vuelta varias veces ( pkt_count ):

struct timespec start, end; for(i = 0; i < pkt_count; ++i) { printf("%d ", i+1); clock_gettime(CLOCK_MONOTONIC, &start); send(sock, send_buf, pkt_size, 0); recv(sock, recv_buf, pkt_size, 0); clock_gettime(CLOCK_MONOTONIC, &end); //struct timespec nsleep = {.tv_sec = 0, .tv_nsec = 100000}; //nanosleep(&nsleep, NULL); printf("%.3f ", timespec_diff_usec(&end, &start)); }

Eliminé las verificaciones de errores y otras cosas menores para mayor claridad. El cliente se ejecuta en Ubuntu 12.04 de 64 bits. Ambos programas se ejecutan en prioridad en tiempo real, aunque solo el núcleo de Ubuntu es en tiempo real (-rt). La conexión entre los programas es TCP. Esto funciona bien y me da un promedio de 750 microsegundos.

Sin embargo, si habilito la llamada nanosleep comentada (con una suspensión de 100 μs), mis medidas caen 100 μs, dando un promedio de 650 μs. Si duermo durante 200 μs, las medidas caen a 550 μs, y así sucesivamente. Esto aumenta hasta un reposo de 600 μs, dando un promedio de 150 μs. Luego, si elevo el sueño a 700 μs, mis medidas van hasta 800 μs en promedio. Confirmé las medidas de mi programa con Wireshark.

No puedo entender lo que está sucediendo. Ya configuré la opción de socket TCP_NODELAY tanto en el cliente como en el servidor, sin diferencias. Usé UDP, no hay diferencia (el mismo comportamiento). Así que supongo que este comportamiento no se debe al algoritmo de Nagle. ¿Qué podría ser?

[ACTUALIZAR]

Aquí hay una captura de pantalla de la salida del cliente junto con Wireshark. Ahora, ejecuté mi servidor en otra máquina. Usé el mismo sistema operativo con la misma configuración (ya que es un Live System en un pen drive), pero el hardware es diferente. Este comportamiento no apareció, todo funcionó como se esperaba. Pero la pregunta sigue siendo: ¿por qué sucede en el hardware anterior?

[ACTUALIZACIÓN 2: más información]

Como dije antes, probé mi par de programas (cliente / servidor) en dos servidores diferentes. Tracé los dos resultados obtenidos.

El primer servidor (el más extraño) es un ordenador de una sola placa RTD , con una interfaz Ethernet de 1 Gbps. El segundo servidor (el normal) es una computadora Diamond Single Board con una interfaz Ethernet de 100Mbps. Ambos ejecutan el MISMO SO (NetBSD 5.1.2) del MISMO pendrive.

A partir de estos resultados, creo que este comportamiento se debe al controlador o al NIC en sí, aunque todavía no puedo imaginar por qué sucede ...


Esta es una conjetura (afortunadamente educada), pero creo que podría explicar lo que está viendo.

No estoy seguro de qué tan real es el kernel de Linux. Puede que no sea totalmente preventivo ... Entonces, con esa cláusula de exención de responsabilidad, continúa:) ...

Dependiendo del programador, una tarea posiblemente tendrá lo que se denomina "quanta", que es solo una cantidad de tiempo que puede ejecutarse antes de que se programe otra tarea de la misma prioridad. Si el kernel no es totalmente preventivo, este también podría ser el punto donde se puede ejecutar una tarea de mayor prioridad. Esto depende de los detalles del planificador que no conozco lo suficiente.

En cualquier momento entre su primer tiempo de obtención y el segundo de su tarea, puede adelantarse. Esto solo significa que está "en pausa" y otra tarea utiliza la CPU durante un cierto período de tiempo.

El ciclo sin el sueño podría ser algo como esto

clock_gettime(CLOCK_MONOTONIC, &start); send(sock, send_buf, pkt_size, 0); recv(sock, recv_buf, pkt_size, 0); clock_gettime(CLOCK_MONOTONIC, &end); printf("%.3f ", timespec_diff_usec(&end, &start)); clock_gettime(CLOCK_MONOTONIC, &start); <----- PREMPTION .. your tasks quanta has run out and the scheduler kicks in ... another task runs for a little while <----- PREMPTION again and your back on the CPU send(sock, send_buf, pkt_size, 0); recv(sock, recv_buf, pkt_size, 0); clock_gettime(CLOCK_MONOTONIC, &end); // Because you got pre-empted, your time measurement is artifically long printf("%.3f ", timespec_diff_usec(&end, &start)); clock_gettime(CLOCK_MONOTONIC, &start); <----- PREMPTION .. your tasks quanta has run out and the scheduler kicks in ... another task runs for a little while <----- PREMPTION again and your back on the CPU and so on....

Cuando pones el nanosegundo en reposo, es muy probable que este sea un punto en el que el programador puede ejecutarse antes de que caduque la quanta de la tarea actual (lo mismo se aplicaría a recv () también, que bloquea). Entonces, quizás lo que obtienes es algo como esto

clock_gettime(CLOCK_MONOTONIC, &start); send(sock, send_buf, pkt_size, 0); recv(sock, recv_buf, pkt_size, 0); clock_gettime(CLOCK_MONOTONIC, &end); struct timespec nsleep = {.tv_sec = 0, .tv_nsec = 100000}; nanosleep(&nsleep, NULL); <----- PREMPTION .. nanosleep allows the scheduler to kick in because this is a pre-emption point ... another task runs for a little while <----- PREMPTION again and your back on the CPU // Now it so happens that because your task got prempted where it did, the time // measurement has not been artifically increased. Your task then can fiish the rest of // it''s quanta printf("%.3f ", timespec_diff_usec(&end, &start)); clock_gettime(CLOCK_MONOTONIC, &start); ... and so on

Entonces se producirá algún tipo de entrelazado en el que a veces se lo elimina entre los dos gettime () ya veces fuera de ellos debido al nanosleep. Dependiendo de x, puede llegar a un punto óptimo donde ocurra (por casualidad) para obtener su punto de preferencia, en promedio, para estar fuera de su bloque de medición de tiempo.

De todos modos, ese es mi valor de dos centavos, espero que ayude a explicar las cosas :)

Una pequeña nota sobre "nanosegundos" para terminar con ...

Creo que hay que ser cauteloso con el sueño de "nanosegundos". La razón por la que digo esto es porque creo que es poco probable que una computadora promedio pueda hacer esto a menos que use hardware especial.

Normalmente, un sistema operativo tendrá un "tic" de sistema regular, generado tal vez en 5 ms. Esta es una interrupción generada por, digamos, un RTC (reloj de tiempo real, solo un poco de hardware). Al usar este "tic", el sistema genera su representación interna en el tiempo. Por lo tanto, el sistema operativo promedio solo tendrá una resolución de tiempo de unos pocos milisegundos. La razón por la que este tic no es más rápido es porque hay que lograr un equilibrio entre mantener un tiempo muy preciso y no saturar el sistema con interrupciones de temporizador.

No estoy seguro de si estoy un poco desactualizado con su PC moderna promedio ... Creo que algunos de ellos tienen temporizadores de alta resolución, pero aún no en el rango de nanosegundos e incluso podrían tener dificultades a 100uS.

Entonces, en resumen, tenga en cuenta que la mejor resolución de tiempo que probablemente obtenga es normalmente en el rango de milisegundos.

EDITAR: Solo volviendo a visitar esto y pensé que agregaría lo siguiente ... no explica lo que está viendo, pero podría proporcionar otra vía de investigación ...

Como se mencionó, la precisión del tiempo del nanosleep es poco probable que sea mejor que milisegundos. También su tarea puede ser eliminada, lo que también causará problemas de tiempo. También existe el problema de que el tiempo que tarda un paquete en subir la pila de protocolos puede variar, así como el retraso de la red.

Una cosa que podría intentar es, si su NIC es compatible, IEEE1588 (también conocido como PTP). Si su NIC lo admite, puede marcar el tiempo de los paquetes de eventos PTP a medida que se van e ingresar el PHY. Esto le dará la mejor estimación del retraso de la red. Esto elimina cualquier problema que pueda tener con la preferencia de software, etc. Sé que estoy en cuclillas sobre Linux PTP, me temo, pero podría intentar http://linuxptp.sourceforge.net/


Si la cantidad de datos que envía la aplicación es lo suficientemente grande y rápida, podría estar llenando los almacenamientos intermedios del kernel, lo que lleva a un retraso en cada envío (). Dado que el sueño está fuera de la sección medida, estaría comiendo el tiempo que de otro modo se pasaría bloqueando en la llamada a send ().

Una forma de ayudar a verificar este caso sería ejecutar con un número relativamente pequeño de iteraciones, y luego un número moderado de iteraciones. Si el problema ocurre con un número pequeño de iteraciones (digamos 20) con tamaños de paquetes pequeños (digamos <1k), entonces es probable que este sea un diagnóstico incorrecto.

Tenga en cuenta que su proceso y kernel pueden abrumar fácilmente el adaptador de red y la velocidad de cable de la ethernet (u otro tipo de medio) si envía datos en un circuito cerrado como este.

Tengo problemas para leer las capturas de pantalla. Si wireshark muestra una tasa constante de transmisión en el cable, sugiere que este es el diagnóstico correcto. Por supuesto, hacer los cálculos matemáticos, dividiendo la velocidad del cable por el tamaño del paquete (encabezado +), debería dar una idea de la velocidad máxima a la que se pueden enviar los paquetes.

En cuanto a los 700 microsegundos que conducen a una mayor demora, eso es más difícil de determinar. No tengo ningún pensamiento sobre eso.


OK, llegué a una conclusión.

Probé mi programa usando Linux, en lugar de NetBSD, en el servidor. Funcionó como se esperaba, es decir, no importa cuánto duerma [nano] en ese punto del código, el resultado es el mismo.

Este hecho me dice que el problema podría estar en el controlador de interfaz de NetBSD. Para identificar el controlador, leo la salida de dmesg . Esta es la parte relevante:

wm0 at pci0 dev 25 function 0: 82801I mobile (AMT) LAN Controller, rev. 3 wm0: interrupting at ioapic0 pin 20 wm0: PCI-Express bus wm0: FLASH wm0: Ethernet address [OMMITED] ukphy0 at wm0 phy 2: Generic IEEE 802.3u media interface ukphy0: OUI 0x000ac2, model 0x000b, rev. 1 ukphy0: 10baseT, 10baseT-FDX, 100baseTX, 100baseTX-FDX, 1000baseT, 1000baseT-FDX, auto

Entonces, como pueden ver, mi interfaz se llama wm0 . De acuerdo con esto (página 9) debo verificar qué controlador está cargado al consultar el archivo sys/dev/pci/files.pci , línea 625 ( aquí ). Muestra:

# Intel i8254x Gigabit Ethernet device wm: ether, ifnet, arp, mii, mii_bitbang attach wm at pci file dev/pci/if_wm.c wm

Luego, buscando en el código fuente del controlador ( dev/pci/if_wm.c , aquí ), encontré un fragmento de código que podría cambiar el comportamiento del controlador:

/* * For N interrupts/sec, set this value to: * 1000000000 / (N * 256). Note that we set the * absolute and packet timer values to this value * divided by 4 to get "simple timer" behavior. */ sc->sc_itr = 1500; /* 2604 ints/sec */ CSR_WRITE(sc, WMREG_ITR, sc->sc_itr);

Luego cambié este valor de 1500 a 1 (tratando de aumentar el número de interrupciones por segundo permitido) y a 0 (tratando de eliminar el estrangulamiento de interrupción por completo), pero ambos valores produjeron el mismo resultado:

  • Sin nanosleep: latencia de ~ 400 us
  • Con un nanosleep de 100 us: latencia de ~ 230 us
  • Con un nanosleep de 200 us: latencia de ~ 120 us
  • Con un nanosleep de 260 us: latencia de ~ 70 us
  • Con un nanosleep de 270 us: latencia de ~ 60 us (latencia mínima que pude lograr)
  • Con un nanosleep de algo superior a 300 us: ~ 420 us.

Esto es, al menos mejor comportamiento que la situación anterior.

Por lo tanto, concluí que el comportamiento se debe al controlador de interfaz del servidor. No estoy dispuesto a investigar más a fondo para encontrar otros culpables, ya que me estoy moviendo de NetBSD a Linux para el proyecto que involucra esta computadora de placa única.


Tengo un consejo sobre cómo crear una medición de rendimiento más precisa. Use la instrucción RDTSC (o incluso mejor la función __rdtsc () intrínseca). Esto implica leer un contador de CPU sin salir del anillo 3 (sin llamada al sistema). Las funciones gettime casi siempre implican una llamada al sistema que ralentiza las cosas.

Su código es un poco complicado ya que involucra 2 llamadas al sistema (envío / recepción), pero en general es mejor llamar al modo reposo (0) antes de la primera medición para asegurarse de que la muy corta medición no reciba un cambio de contexto. Por supuesto, el código de medición del tiempo (y del modo de reposo ()) debe deshabilitarse / habilitarse mediante macros en funciones sensibles al rendimiento.

Algunos sistemas operativos pueden engañarse para aumentar la prioridad de su proceso haciendo que su proceso lo libere en la ventana de tiempo de ejecución (p. Ej., Suspensión (0)). En el siguiente tick de programación, el sistema operativo (no todos) aumentará la prioridad de su proceso ya que no terminó de ejecutar su cuota de tiempo de ejecución.


Creo que ''quanta'' es la mejor teoría para la explicación. En Linux, es la frecuencia de cambio de contexto. Kernel permite procesar el tiempo de los cuantos. Pero el proceso se adelanta en dos situaciones:

  1. Procedimiento del sistema de llamada de proceso
  2. el tiempo de quanta ha terminado
  3. la interrupción de hardware está llegando (de la red, disco duro, usb, reloj, etc.)

El tiempo de quanta no utilizado se asigna a otro proceso listo para ejecutarse, usando prioridades / rt, etc.

En realidad, la frecuencia de cambio de contexto se configura en 10000 veces por segundo, da aproximadamente 100us por cuantos. pero el cambio de contenido lleva un tiempo, depende de la CPU, mira esto: http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html no lo entiendo, ¿Por qué la frecuencia swith de contenido es tan alta, pero es una discusión para Linux kernel forum.

un problema parcialmente similar que puede encontrar aquí: https://serverfault.com/questions/14199/how-many-context-switches-is-normal-as-a-function-of-cpu-cores-or-other