作为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 对这个 16MB 的字符串使用“仅”150 MB RAM(并且需要 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)