遍历一个类对象的数组 VS 包含数组的类对象。

3
我想创建一个多智能体模拟程序,我正在考虑是否应该使用NumPy或numba来加速计算。基本上,我需要一个类来存储智能体的状态,并且我将有1000多个这些类的实例。在每个时间步骤中,我将为所有实例执行不同的计算。我正在考虑两种方法:
Numpy向量化:
使用1个类具有多个NumPy数组来存储所有智能体的状态。因此,在模拟期间,我将仅有1个类实例。通过这种方法,我可以简单地使用NumPy向量化来执行计算。但是,这将使特定智能体的函数运行变得困难,我需要额外的类来存储每个智能体的索引。
Class agent:
   def __init__(self):
      self.A = np.zeros(1000)
      self.B = np.zeros(1000)
   def timestep(self):
      return self.A + self.B

Numba jitclass:

使用numba jitclass装饰器编译代码。采用这种方法,可以将更多标准的OOP格式化代码应用为一个类实例代表一个代理。然而,我不确定循环遍历多个jitclass实例(比如1000个或更多)的性能。

@jitclass
class agent:
   def __init__(self):
      self.A = 0
      self.B = 0
   def timestep(self):
      return self.A + self.B

我想知道哪种方法会更快。

1个回答

2
这个问题被称为“AoS VS SoA”,其中AoS表示结构数组,SoA表示数组结构。你可以在这里找到一些相关信息。SoA比AoS不太用户友好,但通常更高效。特别是当你的代码可以受益于使用SIMD指令时,这一点尤其明显。当你处理许多大数组(例如>=8个大数组)或者进行许多标量随机内存访问时,无论是AoS还是SoA都不是很高效。在这种情况下,最好的解决方案是使用小数组的结构数组(AoSoA),以便更好地利用CPU缓存,同时仍能受益于SIMD。然而,AoSoA很繁琐,对于非平凡算法的代码来说复杂性也相应增加。请注意,在选择最佳解决方案时,所访问的字段数量也很重要(例如,如果只有一个字段经常被读取,则SoA是完美的)。
面向对象编程(OOP)通常在性能方面表现较差,部分原因就在于此。另一个原因是频繁使用虚函数调用和多态性,而并非总是需要这样做。OOP 代码往往会导致大量的缓存未命中,因此优化大规模使用 OOP 的代码通常是一场混乱(有时会导致重写目标软件的大部分或代码运行非常缓慢)。为了解决这个问题,可以使用数据导向设计。这种方法已成功地用于从视频游戏(例如 Unity)到 Web 浏览器渲染器(例如 Chrome)甚至关系型数据库的大型代码库的极大加速。在高性能计算(HPC)中,往往几乎不使用 OOP。面向对象设计与使用 SoA 而不是 AoS 相关,以更好地利用缓存并受益于 SIMD。欲了解更多信息,请阅读相关文章。
总之,我建议你在这种情况下使用第一个代码(SoA)(因为你只有两个数组,而且它们并不是很大)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接