Python:“揭开”一长串异或数据

问题描述 投票:0回答:3

作为WebSocket 规范的一部分,所有客户端发送的帧必须具有使用 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 “仅”使用 150 MB RAM 来处理这个 16MB 字符串(并且需要 20 秒),但这仍然很糟糕。相比之下,我的 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)
python performance websocket xor
3个回答
2
投票

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

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

0
投票

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


0
投票
我发现最快的方法是使用

translate

。我将提供 Python 3 的代码,但如果有人仍然使用 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.719 秒,代码如下:
frame = bytearray() for i in range(len(oldFrame)): frame.append(oldFrame[i] ^ mask[i % 4])

    就地算法排名为1.708 s:
for i in range(len(frame)): frame[i] ^= mask[i % 4]

    针对您的特定用例稍微优化一下,可以得到 1.256 秒:
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

    相比之下,我的代码运行时间为 0.034 秒:
for i in range(4): frame[i::4] = frame[i::4].translate(bytes([x ^ mask[i] for x in range(256)]))
    
© www.soinside.com 2019 - 2024. All rights reserved.