在python中的大型单行文本文件中搜索字符串



我正试图编写一个程序,在一个非常大的文本文件中搜索特定的字符串,然后从文件中返回字符串以及前后一定数量的字符,并能够快速执行数千次。

文件只有一行,大小超过1 GB,我见过的大多数方法都是将文件分解成行。

我想找到一种有效搜索并利用多线程的方法。我一直在尝试使用Polars库,但我不确定它是否只适用于长文本文件。

这是我到目前为止写的,有效,但不是很快

with open(r"file.txt", 'rb', 0) as file:
for date in D:
x = date.encode('UTF-8')
s = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
Pos = s.find(x)
if Pos != -1:
file.seek(Pos-30)
P.append(file.read(60 + len(date)).decode('UTF-8'))

else:
print(date + " was not found")
file.close()

这里有一个使用Polars执行您描述的搜索任务的解决方案根据一些注意事项,它将在机器的所有核心上并行执行任务。。。并且它将显著地快速。(我会在最后提供一个基准。)

注意事项

  • 支持可能会受到限制。在单行文本文件中搜索数千个字符串表示使用Polars DataFrame查询引擎是不典型的。因此,Polars用户社区的支持可能会受到限制。

  • Polars是一个内存中的查询引擎。因此,您的大型单行文本文件必须适合内存。如果您的文本文件太大,无法舒适地放入计算平台上的RAM中,则可能需要批量应用此算法。

  • 升级至Polars0.13.40或更高版本。我们将使用新的extract_all方法。

  • 请确保您的CPU冷却充足根据机器的功能、文本文件的大小和搜索项目的数量,该算法将把CPU上的所有核心都推到100%,可能会持续很长一段时间。(事实上,为了开发这个算法,我不得不应用延迟很长时间的BIOS和BMC更新来确保足够的风扇控制,这延迟了这个响应。)

  • 性能不能保证针对这一特定用途进行优化,也不一定代表Polar更典型用途的预期性能。

算法

文本文件和搜索术语示例

如果我们有一些示例数据,解释算法和结果是最容易的。

作为我们的文本文件,我将使用Polars API文档中的表达式页面。使用浏览器,您可以将HTML文件保存到硬盘驱动器中。

对于我们的搜索项目,我选择了一些常用的Polars表达式和术语。(稍后,为了进行基准测试,我们将使用一个大得多的文本文件和一个长得多的搜索词列表。)

请注意,在Pythonopen语句中,我正在解码文件的内容,并将结果字符串加载到名为txt的列中的PolarsDataFrame中。(特别是,我没有将原始字节加载到DataFrame中。)

import polars as pl
search_terms = [
"select",
"with_column",
"DataFrame",
"explode",
"(E|e)xpression",
"(P|p)olars",
]
with open(r"Expressions — Polars documentation.html", "r") as my_file:
txt_df = pl.DataFrame({"txt": [my_file.read()]})
print(txt_df)
shape: (1, 1)
┌─────────────────────────────────────┐
│ txt                                 │
│ ---                                 │
│ str                                 │
╞═════════════════════════════════════╡
│ logo <https://pola-rs.github.io/... │
└─────────────────────────────────────┘

我们得到的是一行一列的PolarsDataFrame,其中整个文本文件作为字符串加载。

表达式(列表)

表达是Polars的核心掌握表达式的使用是Polars中令人震惊的性能的关键

为了使用,我们将生成一个表达式列表,每个搜索项一个表达式。每个表达式将包含一个正则表达式模式,该模式将包括搜索项以及搜索项前后的10个字符。(您可以根据自己的需要进行更改。)

每个表达式将创建一列("col_1"、"col_2"等),因此我们将为每个搜索项获得一列。

请注意,extract_all表达式将为每个搜索项返回所有正则表达式匹配项的列表。这与问题中的Python代码不同,因为问题中的代码只返回第一个匹配项,而不是所有匹配项。

expr_list = [
pl.col("txt")
.str.extract_all(r".{10}" + search_term + r".{10}")
.alias("col_" + str(col_num))
for col_num, search_term in enumerate(search_terms)
]

运行表达式列表

下面是使用上面的表达式运行搜索的代码,以及为搜索项找到的3356个匹配项。

result = (
txt_df.with_columns(expr_list)
.select(pl.exclude("txt"))
.melt()
.select(["value"])
.with_column(pl.Series(values=search_terms).alias("search_item"))
.explode("value")
)
print(result)
shape: (3356, 2)
┌────────────────────────────┬─────────────┐
│ value                      ┆ search_item │
│ ---                        ┆ ---         │
│ str                        ┆ str         │
╞════════════════════════════╪═════════════╡
│ DataFrame.select_at_idx.ht ┆ select      │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ DataFrame.select_at_idx.ht ┆ select      │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ pulation/ selection <#mani ┆ select      │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ pi/polars.select.html#pola ┆ select      │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ...                        ┆ ...         │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ rence/api/polars.internals ┆ (P|p)olars  │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ian.html> polars.select <h ┆ (P|p)olars  │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ github.io/polars/py-polars ┆ (P|p)olars  │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ rence/api/polars.select.ht ┆ (P|p)olars  │
└────────────────────────────┴─────────────┘

让我们循序渐进

该算法首先运行表达式列表。结果是一行DataFrame。列表中的每个表达式都会创建一列,该列包含每个搜索项的所有匹配项的列表。请注意,第二个搜索项;with _ column";,(col_1),在Polars表达式API页上找不到匹配项。(我特意选择了"with_column"作为搜索项,以显示当找不到匹配项时会发生什么。)

txt_df.with_columns(expr_list)
shape: (1, 7)
┌──────────────────────────────────────┬─────────────────────────────────────┬───────────┬─────────────────────────────────────┬─────────────────────────────────┬─────────────────────────────────────┬─────────────────────────────────────┐
│ txt                                  ┆ col_0                               ┆ col_1     ┆ col_2                               ┆ col_3                           ┆ col_4                               ┆ col_5                               │
│ ---                                  ┆ ---                                 ┆ ---       ┆ ---                                 ┆ ---                             ┆ ---                                 ┆ ---                                 │
│ str                                  ┆ list[str]                           ┆ list[str] ┆ list[str]                           ┆ list[str]                       ┆ list[str]                           ┆ list[str]                           │
╞══════════════════════════════════════╪═════════════════════════════════════╪═══════════╪═════════════════════════════════════╪═════════════════════════════════╪═════════════════════════════════════╪═════════════════════════════════════╡
│ logo <https://pola-rs.github.io/...  ┆ ["DataFrame.select_at_idx.ht", "... ┆ null      ┆ [" o polars.DataFrame.write_csv"... ┆ ["lars.Expr.explode.html#pola"] ┆ ["e used as expression and somet... ┆ ["github.io/polars/py-polars", "... │
└──────────────────────────────────────┴─────────────────────────────────────┴───────────┴─────────────────────────────────────┴─────────────────────────────────┴─────────────────────────────────────┴─────────────────────────────────────┘

在下一步中,我们将使用melt将这个";宽";将多列(且仅一行)的CCD_;"长";格式CCD_ 13。这将把每个搜索项的结果列表放在它自己的行中。

(
txt_df.with_columns(expr_list)
.select(pl.exclude("txt"))
.melt()
)
shape: (6, 2)
┌──────────┬─────────────────────────────────────┐
│ variable ┆ value                               │
│ ---      ┆ ---                                 │
│ str      ┆ list[str]                           │
╞══════════╪═════════════════════════════════════╡
│ col_0    ┆ ["DataFrame.select_at_idx.ht", "... │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ col_1    ┆ null                                │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ col_2    ┆ [" o polars.DataFrame.write_csv"... │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ col_3    ┆ ["lars.Expr.explode.html#pola"]     │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ col_4    ┆ ["e used as expression and somet... │
├╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ col_5    ┆ ["github.io/polars/py-polars", "... │
└──────────┴─────────────────────────────────────┘

接下来,我们将用实际的搜索词替换variable列,这会更有帮助。

(
txt_df.with_columns(expr_list)
.select(pl.exclude("txt"))
.melt()
.select(["value"])
.with_column(pl.Series(values=search_terms).alias("search_item"))
)
shape: (6, 2)
┌─────────────────────────────────────┬────────────────┐
│ value                               ┆ search_item    │
│ ---                                 ┆ ---            │
│ list[str]                           ┆ str            │
╞═════════════════════════════════════╪════════════════╡
│ ["DataFrame.select_at_idx.ht", "... ┆ select         │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ null                                ┆ with_column    │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [" o polars.DataFrame.write_csv"... ┆ DataFrame      │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ["lars.Expr.explode.html#pola"]     ┆ explode        │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ["e used as expression and somet... ┆ (E|e)xpression │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ["github.io/polars/py-polars", "... ┆ (P|p)olars     │
└─────────────────────────────────────┴────────────────┘

最后,我们将使用explode方法,将每个列表中的每个项放置在其自己的行中。

(
txt_df.with_columns(expr_list)
.select(pl.exclude("txt"))
.melt()
.select(["value"])
.with_column(pl.Series(values=search_terms).alias("search_item"))
.explode("value")
)
shape: (3356, 2)
┌────────────────────────────┬─────────────┐
│ value                      ┆ search_item │
│ ---                        ┆ ---         │
│ str                        ┆ str         │
╞════════════════════════════╪═════════════╡
│ DataFrame.select_at_idx.ht ┆ select      │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ DataFrame.select_at_idx.ht ┆ select      │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ pulation/ selection <#mani ┆ select      │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ pi/polars.select.html#pola ┆ select      │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ...                        ┆ ...         │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ rence/api/polars.internals ┆ (P|p)olars  │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ian.html> polars.select <h ┆ (P|p)olars  │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ github.io/polars/py-polars ┆ (P|p)olars  │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ rence/api/polars.select.ht ┆ (P|p)olars  │
└────────────────────────────┴─────────────┘

基准

那么,对于所有这些工作来说,它的速度有多快?

为了进行基准测试,我选择了";enwik9";文件,通常用于涉及文本文件(例如,文件压缩、文本搜索等)的基准测试过程的1GB文件;enwik9";文件是";2006年3月3日英语维基百科转储的前10^9字节";。

对于搜索词,我选择了1969年美国各县的唯一名称(例如"布劳沃德县"、"棕榈滩县"等)

我的计算平台是一个32核的Threadipper Pro(有足够的RAM)。如果一个算法可以并行化,那么Threadipper Pro就会揭示这一点。

我使用你在问题中提供的代码和上面的代码运行了算法。

问题中的代码:439秒

上面的Polars代码:54秒

作为参考,这里是Polars代码的结果(154833匹配所有搜索项):

shape: (154833, 2)
┌─────────────────────────────────────┬───────────────┐
│ value                               ┆ search_item   │
│ ---                                 ┆ ---           │
│ str                                 ┆ str           │
╞═════════════════════════════════════╪═══════════════╡
│ nty]] # [[Cuming County, Nebrask... ┆ Cuming County │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ (East) *[[Cuming County, Nebrask... ┆ Cuming County │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ theast *[[Cuming County, Nebrask... ┆ Cuming County │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ es === *[[Cuming County, Nebrask... ┆ Cuming County │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ...                                 ┆ ...           │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [Category:Roseau County, Minneso... ┆ Roseau County │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ated in [[Roseau County, Minneso... ┆ Roseau County │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [Category:Roseau County, Minneso... ┆ Roseau County │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ hlighting Roseau County.png</tit... ┆ Roseau County │
└─────────────────────────────────────┴───────────────┘

这不是一个苹果对苹果的比较。例如,上面的Polars代码为每个正则表达式模式提取所有匹配项,而不是仅提取第一个匹配项。

如果没有别的,希望这个答案能提供一些我们Polars用户喜欢的令人震惊的快速性能的想法。

最新更新