websocket Python:'Unmask'一个长的XOR数据字符串

vq8itlhq  于 5个月前  发布在  Python
关注(0)|答案(3)|浏览(92)

作为WebSocket spec的一部分,所有客户端发送的帧必须使用4字节掩码屏蔽帧的有效负载部分。在C++中,这非常容易:

for (size_t i = 0; i < length; i++) {
    data[i] ^= mask[i % 4];
}

字符串
可悲的是,Python字符串是不可变的,我宁愿避免这样做,因为不断复制和重新创建字符串缓冲区:

frame = ''
for i in range(0, length-1):
    frame += chr(ord(oldFrame[i]) ^ ord(mask[i % 4]))


经过一番研究,我发现了这个:

m = itertools.cycle(mask)
frame = ''.join(chr(ord(x) ^ ord(y)) for (x,y) in itertools.izip(oldFrame, m))


在CPython中,这将取消屏蔽所需的时间减半。在PyPy中,对于16MB的掩码字符串,这很容易增长到1.5GB RAM的使用,之后它开始交换,我必须杀死它。CPython对这个16MB的字符串“只”使用150 MB RAM相比之下,我的C++基准测试在0.05秒内就完成了,而且没有内存开销。
当然,一旦软件进入生产模式(所有传入数据的上限为10KB),这些极端情况实际上不会发生,但我真的希望在这个基准测试中有一个好分数。
有什么想法吗?唯一的要求是:在CPython和PyPy上都要快,内存使用率低。原始字符串不必保留。
一些测试代码给那些想做实验的人:

import os, time

frame = os.urandom(16 << 20)
mask = os.urandom(4)

def unmask(oldFrame, mask):
    # Do your magic
    return newFrame

for i in range(0, 3): # Run several times, to help PyPy's JIT compiler
    startTime = time.time()
    f = unmask(frame, mask)
    endTime = time.time()
    print 'This run took %.3f seconds' % (endTime - startTime)

vatpfxk5

vatpfxk51#

使用bytearray代替,它是可变的。

frame = bytearray(frame)
for i in range(len(mask)):
    frame[i] ^= mask[i]

字符串

ryevplcw

ryevplcw2#

你也可以考虑numpy,它允许你将操作卸载到numpy中的高效C代码中。这是websockify使用的方法,当numpy不可用时,它会回退到一个较慢的方法,该方法使用支持可变字节数组的数组模块。

mu0hgdu0

mu0hgdu03#

我发现的最快的方法是使用translate。我将提供Python 3的代码,但如果有人仍然使用Python 2,它应该很容易适应Python 2。

def unmask(frame: bytes, mask: bytes):
    for i in range(4):
        frame[i::4] = frame[i::4].translate(bytes([x ^ mask[i] for x in range(256)]))

字符串
下面是我的机器上的方法之间的一些比较:
1.您的原始方法排名为1.719 s,代码如下:

frame = bytearray()
for i in range(len(oldFrame)):
    frame.append(oldFrame[i] ^ mask[i % 4])


1.就地算法的排名为1.708秒:

for i in range(len(frame)):
    frame[i] ^= mask[i % 4]


1.优化它有点为您的特定usecase给出1.256 s:

a, b, c, d = mask
for i in range(0, len(frame), 4):
    frame[i] ^= a
    frame[i + 1] ^= b
    frame[i + 2] ^= c
    frame[i + 3] ^= d


1.相比之下,我的代码运行时间为0.034 s:

for i in range(4):
    frame[i::4] = frame[i::4].translate(bytes([x ^ mask[i] for x in range(256)]))

相关问题