尝试改进Uproot4根树文件输入反序列化的一个分支



我正在使用Uproot访问Python中的根树,当我尝试访问一个特定的分支时,我注意到明显的减速:wf,它包含一个锯齿数组数组

树根树枝

我使用Lazy/Awkward方法访问分支,我使用step_size选项。

LazyFileWF = uproot.lazy('../Layers9_Xe_Phantom102_run1.root:dstree;111', filter_name= "wf",step_size=100)

当我想要访问"LazyFileWF"中的条目时,我经历了6到10秒的慢速。但是如果我移动到下一个连续条目,它只需要大约14毫秒,直到step_size结束。然而,我的脚本需要随机选择条目,而不是顺序,这意味着每个条目将花费我大约8秒的时间来访问。我能够相当快地从其他分支访问数据,除了这个分支,我想找出原因。

通过使用uproot.open().show(),我注意到分支的解释被标记为AsObjects(AsObjects(AsVector(True, AsVector(False, dtype('>f4'))))

我在文档中做了一些挖掘,发现了这个:

Uproot AsObjects Doc

它提到我可以使用simplify来改善缓慢的反序列化。

所以这就是我想知道的,基于我拥有的根树,我可以使用simplify来减少8秒的减速来访问我的分支吗?如果可以,如何实施?有更好的方法来解读这个分支吗?

我试着:

a = uproot.AsObjects.simplify(LazyFileWF.wf)
a

但是我得到一个错误告诉我

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_147439/260639244.py in <module>
5 LazyFileWF = uproot.lazy('../Layers9_Xe_Phantom102_run1.root:dstree;111', filter_name= "wf",step_size=100)
6 events.show(typename_width=35, interpretation_width= 60)
----> 7 a = uproot.AsObjects.simplify(LazyFileWF.wf)
8 a
~/anaconda3/envs/rapids-21.10/lib/python3.7/site-packages/uproot/interpretation/objects.py in simplify(self)
245         ``self``.
246         """
--> 247         if self._branch is not None:
248             try:
249                 return self._model.strided_interpretation(
~/anaconda3/envs/rapids-21.10/lib/python3.7/site-packages/awkward/highlevel.py in __getattr__(self, where)
1129                 raise AttributeError(
1130                     "no field named {0}".format(repr(where))
-> 1131                     + ak._util.exception_suffix(__file__)
1132                 )
1133 
AttributeError: no field named '_branch'
(https://github.com/scikit-hep/awkward-1.0/blob/1.7.0/src/awkward/highlevel.py#L1131)

AsObjects.simplify函数在内部应用于生成您正在使用的默认TBranch.interpretation,如果您在加载TBranch作为数组时不覆盖interpretation。您通过自定义interpretation的唯一原因是,如果默认是错误的,这是一个后门修复的情况下,Uproot自动检测到interpretation不正确。

如果默认的TBranch.interpreation

AsObjects(AsVector(True, AsVector(False, dtype('>f4')))

尝试简化-即用AsStridedObjectsAsJagged替换AsObjects-但不能。这必须是c++std::vector<std::vector<float>>,它每个对象的字节数是可变的,所以没有任何简化的解释可以工作。什么是"simplified"AsStridedObjectsAsJagged的特点是它们每个对象有固定的字节数,因此可以批量解释,而不需要对TBasket中的所有项进行Python的for循环。

顺便说一下,我们在https://arxiv.org/abs/2102.13516中研究了这个确切的案例,而那篇论文中描述的AwkwardForth解决方案将在今年夏天被改编成Uproot。不幸的是,这现在帮不了你。

您所看到的慢速模式是因为每次您请求来自不同TBasket的条目时,Uproot都会解释整个TBasket。如果按顺序运行,则会在每个TBasket开始时看到暂停。惰性数组缓存解释过的数据,所以当你的随机访问返回到之前读过的TBasket时,它应该又快了:只看前几个请求,你会觉得每个请求都很慢,但这只是因为早期的请求比后期的请求更有可能击中未读的TBasket。

如果你只是考虑这个,因为整个过程太慢了(例如,让它运行并填满它的缓存是不够的),那么考虑将整个TBranch读入一个数组并随机访问该数组。如果你的随机访问是在Python循环中(与Numba相反),那么在一个Awkward Array(而不是NumPy数组)上调用__getitem__也没有什么好处,而且会损失一些性能,所以传递library="np"

如果你没有足够的内存来将整个TBranch加载到一个数组中——这可以解释为什么你使用lazy数组——那么你就处于一个困难的位置,因为lazy数组的缓存会对你不利:它会从缓存中驱逐出一段时间没有被访问的tbasket,所以即使是长时间运行的进程也会重复读取/解释。这是对于内存来说太大的数据的随机访问问题中的一个基本问题:没有一个好的方法来缓存它,因为新的请求会不断地将旧的结果挤出缓存。(同样的问题也适用于磁盘访问、web缓存数据、数据库等)

希望,数组适合内存,你可以在内存中随机访问它。笨拙数组的__getitem__比NumPy慢,但它们在内存中更紧凑,所以哪一个最适合你取决于细节。

我希望这些指针有帮助!

最新更新