Python串口单字节读取每64次出现耗时异常的问题求助
问题
在Windows10系统上,使用Python通过USB连接设备,以115200波特率逐字节读取数据时,发现每读取第64个字节时,耗时比其他操作高50-100倍,导致程序运行缓慢甚至出现数据丢失。最初怀疑是append函数的实现问题,但改用预分配列表后问题依旧,目前怀疑问题出在serial.read()函数上,测试代码如下:
import serial import time counter_max = 24*100 test_mode = 0 PORT = 'COM12' BAUDRATE = 115200 uart_port = serial.Serial() uart_port.port = PORT uart_port.baudrate = BAUDRATE uart_port.timeout = None uart_port.open() counter = 0 if test_mode == 0: # --- LIST VERSION ----------------------------------------------------------- elapsed_times = [] uart_datas = [] while counter < counter_max: time_start = time.perf_counter_ns() uart_data = uart_port.read() uart_datas.append(uart_data) time_stop = time.perf_counter_ns() elapsed = time_stop - time_start elapsed_times.append(elapsed) counter += 1 elif test_mode == 1: # --- PREALLOCATED LIST VERSION ---------------------------------------------- elapsed_times = [None] * (counter_max) uart_datas = [None] * (counter_max) while counter < counter_max: time_start = time.perf_counter_ns() uart_data = uart_port.read() uart_datas[counter] = uart_data time_stop = time.perf_counter_ns() elapsed = time_stop - time_start elapsed_times[counter] = elapsed counter += 1 uart_port.close() # print(elapsed_times)
原因分析
- 这是Windows系统串口驱动的64字节默认接收缓冲区特性导致的:Windows串口驱动会将接收到的数据暂存到64字节的缓冲区中,当缓冲区未填满时,
serial.read(1)可以直接从缓冲区中取出单个字节;当缓冲区刚好攒够64字节时,驱动会触发一次完整的缓冲区提交操作,该操作涉及内核态到用户态的数据拷贝,开销远大于单次取字节的操作,因此会出现第64个字节读取耗时暴增的周期性现象。 pyserial库底层依赖Windows的串口API,read(1)的实现会等待驱动返回数据,而驱动的批量缓存策略直接导致了这种周期性的性能波动。
解决办法
1. 批量读取替代逐字节读取
避免每次仅读取1个字节,改为一次性读取多个字节(比如一次读取64字节或更大的块),这样可以减少驱动缓冲区提交操作的触发频率,大幅降低整体耗时。示例代码:
import serial import time counter_max = 24*100 PORT = 'COM12' BAUDRATE = 115200 uart_port = serial.Serial(PORT, BAUDRATE, timeout=None) total_bytes_read = 0 elapsed_times = [] while total_bytes_read < counter_max: time_start = time.perf_counter_ns() # 一次读取64字节,剩余不足64字节时读取剩余量 chunk = uart_port.read(min(64, counter_max - total_bytes_read)) total_bytes_read += len(chunk) time_stop = time.perf_counter_ns() elapsed_times.append(time_stop - time_start) uart_port.close()
2. 调整串口驱动缓冲区大小
在Windows设备管理器中找到对应串口设备,进入属性→端口设置→高级,尝试调大接收缓冲区大小(例如设置为1024字节),减少缓冲区填满的频率,从而缓解周期性耗时问题。注意该操作依赖硬件驱动是否支持自定义缓冲区大小。
3. 异步线程读取解耦
开启独立线程负责持续读取串口数据到内存队列,主线程从队列中获取数据,将串口读取的耗时与业务逻辑解耦,避免单次高耗时读取阻塞主线程。示例代码:
import serial import time import threading from queue import Queue def serial_reader(port, data_queue): while port.is_open: chunk = port.read(64) if chunk: data_queue.put(chunk) counter_max = 24*100 PORT = 'COM12' BAUDRATE = 115200 uart_port = serial.Serial(PORT, BAUDRATE, timeout=1) data_queue = Queue() # 启动后台读取线程 reader_thread = threading.Thread(target=serial_reader, args=(uart_port, data_queue), daemon=True) reader_thread.start() total_bytes_read = 0 elapsed_times = [] while total_bytes_read < counter_max: time_start = time.perf_counter_ns() chunk = data_queue.get() total_bytes_read += len(chunk) time_stop = time.perf_counter_ns() elapsed_times.append(time_stop - time_start) uart_port.close()
内容的提问来源于stack exchange,提问作者booo
相关产品推荐
相关产品推荐

