我试图在Linux环境下编写一个简单的Python脚本来testing以太网交换机,该交换机必须处理包含小型有效载荷(12字节)的高频率UDP消息。
基本的devise是我有一个笔记本电脑(瓦特/ Ubuntu的VirtualBox)运行一个Python脚本使用socket.recvfrom()连接到100Mbps交换机(NETGEAR FS105)。 这个交换机连接到一个桌面PC(另一个Ubuntu VirtualBox),发送包含两个字节的UDP数据包。 这些字节只是一个计数器,它允许我查看数据包传输后是否重新sorting或丢失。
传输代码如下所示:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP period_s = 1 / 5000.0 # 5KHz while True: udp_count = chr ((i >> 8) & 0xFF) + chr( i & 0xFF ) start_time_s = time.time() self.sock.sendto(udp_count, (self.UDP_BROADCAST_IP, self.UDP_PORT)) remaining_idle_time_s = period_s - start_time_s time.sleep(remaining_idle_time_s) i += 1
每隔几秒会发生什么,remaining_idle_time_s返回为负数,因为self.sock.sendto()函数花费的时间超过了0.2ms。
使用cProfile,我可以看到,平均self.sock.sendto()调用需要0.14ms,但有时需要长达3ms! 使用getsockopt()我可以看到,套接字发送缓冲区是足够大(212992)。
我能做些什么来使sock.sendto()返回更快。 我猜测的情况下,需要3ms是由于CPU决定做另一项任务。 有没有办法阻止这个上下文切换我的程序?
简单的答案是,sendto()将花费很长时间,并且不能阻止内核决定进行上下文切换,以便为更高优先级的CPU中断提供服务。 毕竟,这就是操作系统所要做的。 这是什么使Linux,Linux。
我想你可以尝试通过优化内存中的存储来从这块石头中挤出更多的血液,以便通过sendto()传输的缓冲区永远不会跨越页面边界,并始终落入一些方便的缓存行; 也可能与各种可调的Linux内核调度参数混杂在一起,并从中得到一些东西。
无论所有的巫术能达到什么目的,任何人的猜测都和其他人一样好。 也许你可以挤出一个纳秒或太多。 所有的汗水和眼泪都值得吗? 我不知道。
在我看来,如果出色的性能对你来说如此重要,那么你最不希望使用的就是一个通用的操作系统,比如Linux(因为你的问题被标记为“Linux”),而高级,像Python这样的基于虚拟机的语言。 你会希望尽可能地接近金属; 这意味着C,甚至更适合您的应用程序的平台将是一个专门的实时操作系统,从而保证了应用程序的性能。
你想在非实时操作系统上做一个实时任务(Linux不是实时操作系统)。
实时并不意味着快速,而是在特定的时间限制内完成任务:即防止上下文切换是一种方法,因为您的任务时间可以预测。
使用C或编写内核驱动程序来做到这一点,可以使其更快,但不确定 。 有一些像RTLinux这样的硬实时Linux实现,但是如果你的主机不是实时的,那么在虚拟机上运行它是没有意义的。
在Linux中没有办法做到这一点,我非常高兴,因为防止上下文切换的可能性意味着每个应用程序都可以阻止操作系统执行其所谓的重要工作 ….
好,所以我运行了一些更多的测试,正如上面所提到的,这个传输限制从根本上受限于我在一台Windows主机上使用Linux VirtualBox的事实。
即使当我使Windows中的VirtualBox进程成为最高优先级并且使得Python套接字在VirtualBox中调用RT时,有时这个进程仍然会被中断5ms以上。
在本地Linux机器上运行,我可以使用sudo chrt -r -p 99 my_python_process_id
来设置发送python进程的实时性,程序可以以10KHz的速度发送没有问题!