python3 生成大列表时出现内存错误

python3 memoryerror when producing a large list

我是初学者。我最近看到Mandelbrot组非常棒,所以我决定用python画这个组。 但是有一个问题,当我运行这段代码时,我得到了'memoryerror'。

这个语句 num_set = gen_num_set(10000) 会产生一个大列表,大约 20000*20000*4 = 1600000000。当我使用 '1000' 而不是 '10000' 时,我可以 运行 成功编码。

我的电脑内存是4GB,操作系统是window7 32bit。我想知道这个问题是我的电脑限制还是有什么方法可以优化我的代码。

谢谢。

#!/usr/bin/env python3.4

import matplotlib.pyplot as plt
import numpy as np
import random,time
from multiprocessing import *

def first_quadrant(n):
    start_point = 1 / n
    n = 2*n
    return gen_complex_num(start_point,n,1)        

def second_quadrant(n):
    start_point = 1 / n
    n = 2*n
    return gen_complex_num(start_point,n,2)

def third_quadrant(n):
    start_point = 1 / n
    n = 2*n
    return gen_complex_num(start_point,n,3)

def four_quadrant(n):
    start_point = 1 / n
    n = 2*n
    return gen_complex_num(start_point,n,4)

def gen_complex_num(start_point,n,quadrant):
    complex_num = []
    if quadrant == 1:        
        for i in range(n):
            real = i*start_point
            for j in range(n):
                imag = j*start_point
                complex_num.append(complex(real,imag))
        return complex_num
    elif quadrant == 2:
        for i in range(n):
            real = i*start_point*(-1)
            for j in range(n):
                imag = j*start_point
                complex_num.append(complex(real,imag))
        return complex_num
    elif quadrant == 3:
        for i in range(n):
            real = i*start_point*(-1)
            for j in range(n):
                imag = j*start_point*(-1)
                complex_num.append(complex(real,imag))
        return complex_num
    elif quadrant == 4:
        for i in range(n):
            real = i*start_point
            for j in range(n):
                imag = j*start_point*(-1)
                complex_num.append(complex(real,imag))
        return complex_num            

def gen_num_set(n):
    return [first_quadrant(n), second_quadrant(n), third_quadrant(n), four_quadrant(n)]

def if_man_set(num_set):
    iteration_n = 10000
    man_set = []
    z = complex(0,0)
    for c in num_set:
        if_man = 1
        for i in range(iteration_n):
            if abs(z) > 2:
                if_man = 0
                z = complex(0,0)
                break
            z = z*z + c
        if if_man:          
            man_set.append(c)        
    return man_set


def plot_scatter(x,y):
    #plt.plot(x,y)

    color = ran_color()
    plt.scatter(x,y,c=color)
    plt.show()

def ran_num():
    return random.random()

def ran_color():
    return [ran_num() for i in range(3)]

def plot_man_set(man_set):
    z_real = []
    z_imag = []
    for z in man_set:
        z_real.append(z.real)
        z_imag.append(z.imag)
    plot_scatter(z_real,z_imag)


if __name__ == "__main__":
    start_time = time.time()
    num_set = gen_num_set(10000)    
    with Pool(processes=4) as pool:
        #use multiprocess
        set_part = pool.map(if_man_set, num_set)
    man_set = []
    for i in set_part:
        man_set += i
    plot_man_set(man_set)
    end_time = time.time()
    use_time = end_time - start_time
    print(use_time)

你说你正在创建一个包含 16 亿个元素的列表。其中每一个都是一个包含 2 个浮点数的复数。一个 Python 复数需要 24 个字节(至少在我的系统上:sys.getsizeof(complex(1.0,1.0)) 给出 24),所以你需要超过 38GB 的​​空间来存储这些值,而这甚至在你开始之前查看列表本身。

你这个拥有16亿个元素的列表根本放不下32位系统(6.4GB,4字节指针),所以你需要随意转到8字节指针的64位系统仅指针需要 12.8GB。

所以,除非您升级到 64 位 OS,并且可能需要 64GB RAM(尽管可能需要更多),否则您无法做到这一点。

处理这样的大数据时,您应该更喜欢使用 numpy 数组而不是 python 列表。有一个很好的 post 解释原因 (What are the advantages of NumPy over regular Python lists?),但我会尝试总结一下。

在 Python 中,列表中的每个复数都是一个对象(具有方法和属性),因此会占用一些开销 space。这就是为什么它们占用 24 个字节(正如 Duncan 指出的那样)而不是 2 * 32bit 每个复数的两个浮点数。

Numpy 数组建立在 c 风格数组的基础上(基本上所有值都作为原始数字而不是对象在内存中相邻写入)。它们不提供 python 列表的一些不错的功能(如追加),并且仅限于一种数据类型。不过,它们节省了很多 space,因为您不需要节省对象的开销。这将每个复数所需的 space 从 24 字节减少到 8 字节(两个浮点数,每个 32 位)。

虽然 Duncan 是对的,而且您尝试的大实例不会 运行 即使使用 numpy,它也可能会帮助您处理更大的实例。

由于您已经导入了 numpy,因此您可以更改代码以改为使用 numpy 数组。请注意,我对 numpy 不是很精通,而且肯定有更好的方法可以做到这一点,但这是一个对您的原始代码仅作少量更改的示例:

def gen_complex_num_np(start_point, n, quadrant):
    # create a nxn array of complex numbers
    complex_num = np.ndarray(shape=(n,n), dtype=np.complex64) 
    if quadrant == 1:        
        for i in range(n):
            real = i*start_point
            for j in range(n):
                imag = j*start_point
                # fill ony entry in the array
                complex_num[i,j] = complex(real,imag) 
        # concatenate the array rows to 
        # get a list-like return value again
        return complex_num.flatten() 
    ...

此处您的 Python 列表被替换为数据类型为复杂的 2d-numpy 数组。数组被填充后,它被展平(所有行向量被连接起来)以模仿您的 return 格式。

请注意,您必须相应地更改程序所有其他部分中的 man_set 列表。

希望对您有所帮助。