有没有办法在Python中进行4字节对齐正则表达式搜索?

ryhaxcpt  于 2023-03-07  发布在  Python
关注(0)|答案(2)|浏览(226)

当从内存的某个区域读取数据时,结果是字节串的形式,通常我们关心的数据是由4个字节组成的(是int或float类型),并且数据的地址可以被4整除。现在,可以使用Python中的regex模式找到数据的地址(使用像re. findall或re. finditer这样的函数),有没有办法通过每4个字节搜索一次模式来更快地做到这一点?
我知道这可以在CheatEngine这样的软件中实现,但我还没有找到直接在Python中实现的方法,使用4步循环和re.match相当慢。
示例:在中查找pattern = b'\x01\x02\x03\x04'的匹配项(通常是非字符串模式)
bytestr = b'\xa1\x02\x03\x04\x01\x02\x03\x04\xb1\x02\x03\x04'
在这里,它出现在索引4处。由于我们知道模式只能出现在索引0、4、8 ...处,因此我们不使用re.findall(pattern, bytestr),而是使用类似于

for ind in range(0, len(bytestr), 4):
    if re.match(pattern, bytestr[ind : ind + 4]) is not None:
        print(ind)

但是当bytestr很大的时候,它变得相当慢,甚至比re.findall(pattern, bytestr)还慢,有没有一种直接的方法可以用Python实现这种改进?

我在程序中使用的真正模式是

pattern = br"\x93\x5F\x01\x00\x01\x00\x00\x00.....[\x00-\xea]\x00\x00\x60\xea\x00\x00"
o8x7eapl

o8x7eapl1#

The Greatest Regex Trick Ever的变体:不仅匹配您想要的内容,而且作为后备,匹配任意四个字节。

def indexes(pattern, bytestr):
    for i, match in enumerate(re.findall(b'(%b)|....' % pattern, bytestr)):
        if match:
            yield i * 4

对于您的示例,re.findall返回[b'', b'\x01\x02\x03\x04', b''],您只需要报告所需的匹配项并忽略不需要的匹配项。
这要求模式总是精确匹配4个字节(的倍数)。如果不是这样,也许您可以根据需要将.附加到您的模式中,使其成为这样。或者...我想我可以将模式放入一个前瞻中...等等...
好的......这里有一个版本,它总是匹配四个字节,但是在每个位置也会尝试以积极的前瞻方式匹配您想要的模式(回退到匹配空字符串,这样就可以保证匹配并且不干扰进程)。

def indexes(pattern, bytestr):
    for i, match in enumerate(re.findall(b'(?=(%b)|)....' % pattern, bytestr)):
        if match:
            yield i * 4

嗯......它比你的原始版本快,但不是很多。而且只比你的简单优化版本快一点点。下面是一个1 MB字节串中模式出现1000次的时间:

1000 251 ms  original
1000  81 ms  original_optimized
1000  43 ms  Kelly1
1000  44 ms  Kelly2
1000  54 ms  Kelly3

1000 244 ms  original
1000  90 ms  original_optimized
1000  41 ms  Kelly1
1000  45 ms  Kelly2
1000  45 ms  Kelly3

1000 264 ms  original
1000  87 ms  original_optimized
1000  39 ms  Kelly1
1000  41 ms  Kelly2
1000  42 ms  Kelly3

Kelly1和Kelly2就是上面的例子。Kelly3是另一个想法,我构建了一个str,在每个四字节的块前面加上一个“非字节”字符,然后使用它来锚模式。这避免了像我的其他解决方案中那样过滤掉错误匹配。但是它只在模式匹配最多四个字节时有效,而更新的问题现在显示不是这样。此外,速度不够快,所以我没有完全开发它。
准则代码(未清理):

import re

def original(pattern, bytestr):
    for ind in range(0, len(bytestr), 4):
        if re.match(pattern, bytestr[ind : ind + 4]) is not None:
            yield ind

def original_optimized(pattern, bytestr):
    match = re.compile(pattern).match
    for ind in range(0, len(bytestr), 4):
        if match(bytestr[ind : ind + 4]):
            yield ind

def Kelly1(pattern, bytestr):
    for i, match in enumerate(re.findall(b'(%b)|....' % pattern, bytestr)):
        if match:
            yield i * 4

def Kelly2(pattern, bytestr):
    for i, match in enumerate(re.findall(b'(?=(%b)|)....' % pattern, bytestr)):
        if match:
            yield i * 4

def Kelly3(pattern, bytestr):
    s = bytestr.decode('latin1')
    a = len(bytestr) * 5 // 4 * [chr(256)]
    for i in range(4):
        a[i+1::5] = s[i::4]
    s = ''.join(a)
    return re.findall(chr(256) + re.escape(pattern.decode('latin1')), s)

funcs = original, original_optimized, Kelly1, Kelly2, Kelly3

pattern = b'\x01\x02\x03\x04'
bytestr = b'\xa1\x02\x03\x04\x01\x02\x03\x04\xb1\x02\x03\x04'

bytestr = (bytestr + bytes(1000)) * 1000

#pattern = b'abc'
#bytestr = b'1234abcd1abc1234' * 2
args = pattern, bytestr

if 0:
  print(re.findall(b'(?=(%b)|)....' % pattern, bytestr))
  for match in re.finditer(b'(?=(%b)|)....' % pattern, bytestr):
    print(match)

from time import time
for _ in range(3):
  for f in funcs:
    t = time()
    print(len(list(f(*args))), f'{round((time() - t) * 1e3):3} ms ', f.__name__)
  print()

Attempt This Online!

krugob8w

krugob8w2#

我们可以使用该算法来提高搜索的性能。

for i in range(0, len(bytestr), 4):
    if pattern == bytestr[i:i+4]
        print("Location of pattern: %d" %i)

这完成了两件事。
首先,它消除了re.match函数调用产生的开销(我们使用==操作符节省了CPU周期和内存)。[1]
其次,它比re.findall更高效,因为我们将搜索限制在字符串中距离原点为4的倍数的位置。
(The re.findall函数检查字符串中的每个索引是否匹配,而上面的代码只检查4的倍数的索引。)
另一种提高效率的方法是用C/C ++编写代码。
脚注:
[1]如果我们有一个非常长的字节串,并且在每个4的倍数索引处调用re.match,那么我们最终会多次调用re.match,这会产生大量的开销。

相关问题