python调用二进制程序-标准输入输出流重定向

Posted by Borg on November 12, 2019

背景介绍

fastalign 是自然语言处理领域的一个词对齐工具,需要对 fastalign 的训练、模型存储、文本/单句对齐(模型使用)等步骤封装成自动化的 web 平台。fastalign 算法本身没有 python 实现,尝试过将源代码用 python 重写,但发现与官方的二进制程序相比性能差别极大,特别是当需要对齐的数据达上千万句子时,这个性能差距会被放大地更加明显。

调研

仔细观察 fast_align 的源代码会发现里面有个 force_align.py 的脚本,里面虽然没有 fast_align 算法的 python 实现,但该脚本其实是调用了二进制程序,python 读入句子,再通过标准输入流的重定向把输入传送给二进制程序,再从二进制程序的标准输出读出结果。其核心代码摘录如下:

def popen_io(cmd):
    p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    def consume(s):
        for _ in s:
            pass
    threading.Thread(target=consume, args=(p.stderr,)).start()
    return p

fwd_cmd = [fast_align, '-i', '-', '-d', '-T', fwd_T, '-m', fwd_m, '-f', fwd_params]
fwd_align = popen_io(fwd_cmd)
fwd_align.stdin.write('{}\n'.format(line))
fwd_line = fwd_align.stdout.readline().split('|||')[2].strip()

即用 subprocess.Popen 去开启二进制程序作为子进程存储为 fwd_align,通过 fwd_align 的 stdin, stdout 就可以与二进制程序交互。

适配 python 3

官方的 force_align.py 确实可用,但是在 python3 环境下则会出现编码错误,以及输入之后 fwd_align 一直收不到输入数据的错误。通过调查 subprocess.Popen 的文档发现有两个参数需要指定才能正常工作。

  1. buffsize: 缓冲区大小,python3 下默认等于 io.DEFAULT_BUFFER_SIZE,我的机器上是8192。根据文档,python3.2 之前的默认值是0,即不缓冲,因此上面的 force_align.py 才能正常运行。而现在则需要将 buffsize 设置为 1,即行缓冲,写完一行后,子进程就可以取到数据,否则默认情况下一行数据未达到缓冲大小,子进程就没法取到输入。

  2. universal_newlines: universal_newlines 为 True 时 buffsize=1 才会生效,并且会以文本模式打开标准输入输出,即对标准输入输出写入写出的数据格式是 python3 中的 str 字符串,否则是以二进制流打开,写入写出的是 bytes 。

注意事项

用该种方式封装 fast_align 模型调用需要注意的是二进制文件读取一行后给出一行输出,该过程中间不能穿插其他输入,因此封装出来的模型是非线性安全的,同时只能处理一条数据,多线程下需要用锁对模型进行保护。还有输入注意以行为单位,需要确保输入的每句话以换行符结尾。