遍历 class 个对象的数组 VS class 个包含数组的对象

Iterating over an array of class objects VS a class object containing arrays

我想创建一个多代理模拟程序,我在考虑是否应该使用 NumPy 或 numba 来加速计算。基本上,我需要一个 class 来存储代理的状态,并且我将拥有超过 1000 个 class 实例。在每个时间步中,我将对所有实例执行不同的计算。我正在考虑两种方法:

Numpy 向量化:

有 1 个 class 和多个 NumPy 数组,用于存储所有代理的状态。因此,在模拟过程中,我始终只有 1 class 个实例。通过这种方法,我可以简单地使用 NumPy 向量化来执行计算。但是,这将使特定代理的 运行 功能变得困难,我需要一个额外的 class 来存储每个代理的索引。

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 格式代码,因为一个 class 实例代表一个代理。但是,我不确定循环多个 jitclass 实例(比如说 1000 或更多)的性能。

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

我想知道哪种方法更快。

这个问题被称为 “AoS VS SoA”,其中 AoS 表示结构数组,SoA 表示数组结构。您可以找到有关此 here 的一些信息。 SoA 比 AoS 少 user-friendly,但通常效率更高。当您的代码可以受益于使用 SIMD 指令 时尤其如此。当您处理许多大数组(例如 >=8 个大数组)或执行许多标量随机内存访问时,AoS 和 SoA 都不是高效的。在这种情况下,最好的解决方案是使用小型数组结构数组 (AoSoA),以便更好地使用 CPU 缓存,同时仍然能够从 SIMD 中受益。然而,AoSoA 很乏味,因为复杂性明显是非平凡算法的代码。请注意,访问的字段数量在最佳解决方案的选择中也很重要(例如,如果只有一个字段被频繁读取,那么 SoA 是完美的)。

OOP is generally rather bad when it comes to performance partially because of this. Another reason is the frequent use of virtual calls and polymorphism while it is not always needed. OOP codes tends to cause a lot of cache misses and optimizing a large code that massively use OOP is often a mess (which sometimes results in rewriting a big part of the target software or the code being left very slow). To address this problem, data oriented design can be used. This approach has been successfully used to drastically speed up large code bases from video games (eg. Unity) to web browser renderers (eg. Chrome) and even relational databases. In high-performance computing (HPC), OOP is often barely used. Object-oriented design is quite related to the use of SoA rather than AoS so to better use cache and benefit from SIMD. For more information, please read this 相关 post.

最后,我建议您在您的案例中使用第一个代码 (SoA)(因为您只有两个数组,而且它们不是那么大)。