11

我正在尝试确定机器接收数据包、处理数据包并返回答案所需的时间。

这台机器,我称之为“服务器”,运行一个非常简单的程序,它在缓冲区中接收数据包(recv(2)),将接收到的内容(memcpy(3))复制到另一个缓冲区,然后将数据包发回(send(2))。服务器运行 NetBSD 5.1.2。

我的客户多次测量往返时间(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));
}   

为了清楚起见,我删除了错误检查和其他小事。客户端在 Ubuntu 12.04 64 位上运行。这两个程序都以实时优先级运行,尽管只有 Ubuntu 内核是实时的 (-rt)。程序之间的连接是 TCP。这工作正常,平均给我 750 微秒。

但是,如果我启用注释掉的 nanosleep 调用(睡眠时间为 100 µs),我的测量值会下降 100 µs,平均为 650 µs。如果我睡了 200 µs,测量值会下降到 550 µs,依此类推。这一直持续到 600 µs 的睡眠,平均为 150 µs。然后,如果我将睡眠时间提高到 700 µs,我的测量值平均会上升到 800 µs。我用 Wireshark 确认了我的程序的措施。

我无法弄清楚发生了什么。我已经在客户端和服务器中设置了 TCP_NODELAY 套接字选项,没有区别。我使用了UDP,没有区别(相同的行为)。所以我猜这种行为不是由于 Nagle 算法。会是什么呢?

[更新]

这是客户端与 Wireshark 一起输出的屏幕截图。现在,我在另一台机器上运行我的服务器。我使用具有相同配置的相同操作系统(因为它是笔式驱动器中的 Live System),但硬件不同。这种行为没有出现,一切都按预期工作。但问题仍然存在:为什么它会发生在以前的硬件中?

输出比较

[更新 2:更多信息]

正如我之前所说,我在两台不同的服务器计算机上测试了我的一对程序(客户端/服务器)。我绘制了获得的两个结果。

两台服务器之间的比较

第一台服务器(奇怪的)是一台RTD 单板计算机,具有 1Gbps 以太网接口。第二台服务器(普通服务器)是具有 100Mbps 以太网接口的Diamond 单板计算机。他们都从相同的 pendrive 运行相同的操作系统 (NetBSD 5.1.2)。

从这些结果来看,我确实相信这种行为是由于驱动程序或网卡本身造成的,尽管我仍然无法想象为什么会发生这种情况......

4

5 回答 5

2

好的,我得出了一个结论。

我在服务器中使用 Linux 而不是 NetBSD 尝试了我的程序。它按预期运行,即无论我在代码的那个点[nano]睡了多少,结果都是一样的。

这个事实告诉我,问题可能出在 NetBSD 的接口驱动程序上。为了识别驱动程序,我读取了dmesg输出。这是相关部分:

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

所以,如你所见,我的接口被称为wm0. 根据这个(第 9 页),我应该通过查阅文件sys/dev/pci/files.pci第 625 行(此处)来检查加载了哪个驱动程序。表明:

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

然后,搜索驱动程序源代码(dev/pci/if_wm.c这里),我发现了一段可能会改变驱动程序行为的代码片段:

/*
 * 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);

然后我将这个 1500 值更改为 1(试图增加每秒允许的中断数)和 0(试图完全消除中断限制),但这两个值产生了相同的结果:

  • 没有 nanosleep:延迟约为 400 us
  • 具有 100 us 的 nanosleep:约 230 us 的延迟
  • 具有 200 us 的 nanosleep:延迟约 120 us
  • 具有 260 us 的 nanosleep:约 70 us 的延迟
  • 使用 270 us 的 nanosleep:延迟约 60 us(我可以达到的最小延迟)
  • 任何超过 300 us 的 nanosleep:~420 us

这至少比以前的情况表现得更好。

因此,我得出结论,该行为是由于服务器的接口驱动程序造成的。我不愿意进一步调查它以找到其他罪魁祸首,因为我正在从 NetBSD 转移到 Linux 以完成涉及这台单板计算机的项目。

于 2013-09-05T20:50:01.730 回答
0

我对如何创建更准确的绩效衡量有一个建议。使用 RDTSC 指令(甚至更好的内在 __rdtsc() 函数)。这涉及在不离开 ring3 的情况下读取 CPU 计数器(无系统调用)。gettime 函数几乎总是涉及减慢速度的系统调用。

您的代码有点棘手,因为它涉及 2 个系统调用(发送/接收),但通常最好在第一次测量之前调用 sleep(0) 以确保非常短的测量不会收到上下文切换。当然,时间测量(和 Sleep())代码应该通过性能敏感函数中的宏禁用/启用。

一些操作系统可以通过让你的进程释放它的执行时间窗口来提高你的进程优先级(例如sleep(0))。在下一个计划滴答声中,操作系统(不是全部)将提高您的进程的优先级,因为它没有完成运行其执行时间配额。

于 2013-11-21T13:29:28.597 回答
0

我认为“量子”是最好的解释理论。在 linux 上,它是上下文切换频率。内核提供处理量子时间。但是进程在两种情况下被抢占:

  1. 进程调用系统过程
  2. 量子时间结束
  3. 硬件中断即将到来(来自网络、硬盘、USB、时钟等...)

未使用的量子时间分配给另一个准备运行的进程,使用优先级/rt等。

实际上上下文切换频率配置为每秒 10000 次,它为 quanta 提供了大约 100us。但内容切换需要时间,这取决于 cpu,请参阅: http ://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html 我不明白,为什么内容交换频率那么高,但它是 linux 内核论坛的讨论。

您可以在这里找到部分类似的问题: https ://serverfault.com/questions/14199/how-many-context-switches-is-normal-as-a-function-of-cpu-cores-or-other

于 2013-07-11T09:28:23.357 回答
0

这是一个(希望是受过教育的)猜测,但我认为它可以解释你所看到的。

我不确定linux内核的实时性。它可能不是完全先发制人的......所以,带着这个免责声明,继续:)......

根据调度程序,一个任务可能会有所谓的“量子”,这只是在另一个相同优先级的任务将被调度之前它可以运行的时间量。如果内核不是完全抢占式的,这也可能是更高优先级任务可以运行的点。这取决于我不太了解的调度程序的详细信息。

在您的第一次 gettime 和第二次 gettime 之间的任何时间,您的任务都可能被抢占。这只是意味着它被“暂停”并且另一个任务在一定时间内使用 CPU。

没有睡眠的循环可能会像这样

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....

当您将纳秒睡眠置于其中时,这很可能是调度程序能够在当前任务的量子到期之前运行的点(这同样适用于 recv() ,它会阻塞)。所以也许你得到的是这样的

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

然后会发生某种交错,有时您会在两个 gettime() 之间被抢占,有时由于 nanosleep 而在它们之外。根据 x,您可能会碰巧(偶然)使您的抢占点平均在您的时间测量块之外。

无论如何,这是我的两便士价值,希望它有助于解释事情:)

关于“纳秒”的一点说明,以......

我认为需要对“纳秒”睡眠保持谨慎。我这么说的原因是我认为普通计算机不太可能真正做到这一点,除非它使用特殊的硬件。

通常一个操作系统会有一个常规的系统“tick”,大概在 5ms 生成。这是由 RTC(实时时钟 - 只是一点硬件)生成的中断。使用这个“tick”系统然后生成它的内部时间表示。因此,平均操作系统将只有几毫秒的时间分辨率。这个滴答声不快的原因是要在保持非常准确的时间和不让定时器中断淹没系统之间取得平衡。

不确定我是否与您的普通现代 PC 有点过时了……我认为其中一些确实具有更高的分辨率计时器,但仍不在纳秒范围内,甚至可能在 100uS 时挣扎。

因此,总而言之,请记住,您可能获得的最佳时间分辨率通常在毫秒范围内。

编辑:只是重新审视这一点,并认为我会添加以下内容......并不能解释您所看到的内容,但可能会提供另一种调查途径......

如前所述, nanosleep 的计时精度不太可能优于毫秒。此外,您的任务可能会被抢占,这也会导致时间问题。还有一个问题是数据包上协议栈所需的时间可能会有所不同,以及网络延迟。

您可以尝试的一件事是,如果您的 NIC 支持 IEEE1588(又名 PTP)。如果您的 NIC 支持它,它可以在 PTP 事件数据包离开和进入 PHY 时为其添加时间戳。这将为您提供网络延迟的最佳估计值。这消除了您在软件抢占等方面可能遇到的任何问题。恐怕我对 Linux PTP 深有体会,但您可以尝试http://linuxptp.sourceforge.net/

于 2013-04-28T11:53:15.440 回答
0

如果应用程序发送的数据量足够大且足够快,它可能会填充内核缓冲区,这会导致每个 send() 出现延迟。由于睡眠在测量部分之外,因此它将占用原本会阻塞在 send() 调用上的时间。

帮助检查这种情况的一种方法是运行相对较少的迭代次数,然后进行中等次数的迭代。如果问题发生在具有小数据包大小(例如 <1k)的少量迭代(例如 20 次)中,那么这可能是错误的诊断。

请记住,如果在这样的紧密循环中发送数据,您的进程和内核很容易压倒网络适配器和以太网(或其他媒体类型)的线速。

我在阅读屏幕截图时遇到问题。如果wireshark 在网络上显示出恒定的传输速率,则表明这是正确的诊断。当然,进行数学运算 - 将线速除以数据包大小(+ 标头) - 应该可以了解可以发送数据包的最大速率。

至于导致延迟增加的 700 微秒,那就更难确定了。我对那个没有任何想法。

于 2013-08-28T22:58:59.783 回答