我正试图编写一个程序,在一个非常大的文本文件中搜索特定的字符串,然后从文件中返回字符串以及前后一定数量的字符,并能够快速执行数千次。
文件只有一行,大小超过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中,则可能需要批量应用此算法。
-
升级至Polars
0.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用户喜欢的令人震惊的快速性能的想法。