Python中是否有无锁只读列表?

9 投票
1 回答
2554 浏览
提问于 2025-04-16 10:19

我做了一些基本的性能和内存消耗测试,想知道有没有办法让事情变得更快...

  1. 我有一个包含70,000个元素的大列表,里面是一个numpy的ndarray,以及这个列表中的文件路径以元组的形式存储。

  2. 我第一版的做法是把这个列表切片后传给每个进程,但这样会导致内存使用量飙升到超过20GB。

  3. 第二版我把它放到了全局空间,通过索引像foo[i]这样在每个进程的循环中访问,这样似乎把它放到了共享内存区域,进程之间的内存使用没有爆炸(保持在大约3GB)。

  4. 不过根据性能基准测试和跟踪,似乎大部分时间都花在了“获取”模式上...

所以我在想有没有办法把这个列表变成某种无锁/只读的状态,这样我就可以省去一部分获取的步骤,从而进一步加快访问速度。

编辑 1:这是应用程序性能分析的前几行输出:

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   65 2450.903   37.706 2450.903   37.706 {built-in method acquire}
39320    0.481    0.000    0.481    0.000 {method 'read' of 'file' objects}
  600    0.298    0.000    0.298    0.000 {posix.waitpid}
   48    0.271    0.006    0.271    0.006 {posix.fork}

编辑 2:这是列表结构的一个例子:

# Sample code for a rough idea of how the list is constructed
sim = []
for root, dirs, files in os.walk(rootdir):
    path = os.path.join(root, filename)
    image= Image.open(path)
    np_array = np.asarray(image)
    sim.append( (np_array, path) )

# Roughly it would look something like say this below
sim = List( (np.array([[1, 2, 3], [4, 5, 6]], np.int32), "/foobar/com/what.something") )

因此,从此以后,SIM列表将是只读的。

1 个回答

11

multiprocessing模块正好提供了你需要的功能:一个可以共享的数组,并且可以选择是否加锁,这个功能是通过multiprocessing.Array类来实现的。如果你不想加锁,可以在创建的时候传入lock=False

编辑(考虑到你的更新):事情比我最开始想的要复杂得多。你列表中所有元素的数据需要在共享内存中创建。把列表本身(也就是指向实际数据的指针)放在共享内存中并不是特别重要,因为这部分数据相对于所有文件的数据来说是比较小的。要把文件数据存储在共享内存中,可以使用

shared_data = multiprocessing.sharedctypes.RawArray("c", data)

这里的data是你从文件中读取的数据。要在某个进程中将其作为NumPy数组使用,可以用

numpy.frombuffer(shared_data, dtype="c")

这样可以为共享数据创建一个NumPy数组的视图。同样,要把路径名放入共享内存,可以使用

shared_path = multiprocessing.sharedctypes.RawArray("c", path)

这里的path是一个普通的Python字符串。在你的进程中,你可以通过shared_path.raw来访问这个字符串。现在把(shared_data, shared_path)添加到你的列表中。这个列表会被复制到其他进程中,但实际的数据不会被复制。

我最开始打算用multiprocessing.Array来存储实际的列表。这是完全可行的,并且可以确保列表本身(也就是指向数据的指针)也在共享内存中。但现在我觉得这并不是特别重要,只要实际的数据是共享的就可以了。

撰写回答