代码之家  ›  专栏  ›  技术社区  ›  user5071535

如何减少4M+带字符串对象的字典的内存占用?

  •  1
  • user5071535  · 技术社区  · 7 年前

    如何减少包含4M多个带字符串对象的字典的内存占用?

    它目前消耗大约1.5gbytes的RAM,我需要在资源有限的系统上添加数百万个对象,因为成本高昂(基于云)。

    简化代码

    import pymysql
    
    class User:
        __slots__ = ['user_id', 'name', 'type']
        def __init__(self):
            user_id = None
            name = None
            type = None
    
    cursor.execute("SELECT UserId, Username, Type FROM Users")
    db_query_result = cursor.fetchall()
    
    all_users = {}
    
    for db_user in db_query_result:
    
        user_details = User()
        user_details.name = db_user[1]
        user_details.type = db_user[2]
    
        db_user_id = db_user[0]
    
        all_users[str(db_user_id)] = user_details
    

    数据类型

    • 用户id:int
    • 类型:int

    在某些web搜索中,由于字符串对象需要大量内存,User.name似乎占用了大部分空间。

    __slots__ ,但我需要进一步减少。

    4 回复  |  直到 7 年前
        1
  •  2
  •   James Lim    7 年前

    如果确实需要本地数据,请考虑将其保存到主机上的SQLite数据库,并让SQLite将热数据集加载到内存中,而不是将其全部保存在内存中。

    db_conn = sqlite3.connect(path_to_sqlite_file)
    db_conn.execute('PRAGMA mmap_size={};'.format(mmap_size))
    

    如果您真的需要内存中的所有数据,请考虑在主机上配置交换空间,作为一种更便宜的选择。操作系统会将较冷的内存页交换到此交换空间。

    当然,如果 name

    也可以使用结构而不是类。

    sys.getsizeof(u)  # 64 bytes
    sys.getsizeof(struct.pack('HB13s', 10, 1, b'raymond'))  # 49 bytes
    # unsigned short for user ID, unsigned byte for type, string with 13 bytes
    

    如果您知道您的用户id是连续的,并且您使用的是固定长度的结构,那么您还可以通过计算字节偏移来查找简单数组,而不是使用dict(Numpy数组在这里很有用)

    all_users = np.array([structs])
    all_users = (struct0, struct1, struct2, ...)  # good old tuples are OK too e.g. all_users[user_id] would work
    

    # writing
    with open('file.dat', mode='w+') as f:
        for user in users:
            f.write(user)  # where user is a fixed length struct
    
    # reading
    with open('file.dat', mode='r') as f:
        # given some index
        offset = index * length_of_struct
        f.seek(offset)
        struct = f.read(length_of_struct)
    

    但是,我不认为这是解决实际问题的最佳设计。其他备选方案包括:

    • 检查数据库设计,特别是索引
        2
  •  2
  •   abarnert    7 年前

    一个13个字符的字符串如果全部是拉丁语,实际的字符串存储只需要13个字节,如果全部是BMP,则需要26个字节,如果整个Unicode中都有字符,则需要52个字节。

    但是 str


    如果字符串一旦被编码为UTF-8或UTF-16-LE或任何最适合您的数据的格式,那么您可能希望将它们存储在一个大的平面数组中,并根据需要将它们取出并动态解码,如 James Lim's answer . 尽管我可能会使用NumPy原生结构化数据类型,而不是使用 struct 模块。

    那么你想要一个字符串表。这只是个巨人 bytearray 所有(编码的)字符串所在的位置,您将索引存储到该表中,而不是存储字符串本身。这些索引只是 int32 int64 可以打包到数组中而没有问题的值。

    例如,假设您的字符串都不超过255个字符,我们可以将它们存储为“Pascal strings”,长度字节后跟编码字节:

    class StringTable:
        def __init__(self):
            self._table = bytearray()
        def add(self, s):
            b = s.encode()
            idx = len(self._table)
            self._table.append(len(b))
            self._table.extend(b)
            return idx
        def get(idx):
            stop = idx + self._table[idx]
            return self._table[idx+1:stop].decode()
    

    strings = StringTable()
    
    for db_user in db_query_result:
    
        user_details = User()
        user_details.name = strings.add(db_user[1])
        user_details.type = strings.add(db_user[2])
    
        db_user_id = strings.add(str(db_user[0]))
    
        all_users[db_user_id] = user_details
    

    当然,你可能还是想换掉它 all_users 一个核阵列。

        3
  •  1
  •   blhsing    7 年前

    而不是使用 cursor.fetchall() ,在客户端存储所有数据时,应使用 SSCursor 要将结果集保留在服务器端,请执行以下操作:

    import pymysql
    import pymysql.cursors as cursors
    
    conn = pymysql.connect(..., cursorclass=cursors.SSCursor)
    

    以便您可以逐个获取行:

    cursor = conn.cursor()
    cursor.execute('SELECT UserId, Username, Type FROM Users')
    for db_user in cursor:
        user_details = User()
        user_details.name = db_user[1]
        user_details.type = db_user[2]
        ...
    

    all_users dict,您可能也不需要在dict中存储所有用户信息。如果可以逐个处理每个用户,请直接在 for 在上面循环而不是建立一个巨大的dict。

        4
  •  1
  •   abarnert    7 年前

    记忆中 ,或者只是 论地方制度 ?

    如果是后者,只需使用本地数据库。

    key-value database . 最简单的KV数据库是 dbm ,Python支持开箱即用。使用 从Python看来,除了数据在磁盘上而不是内存中之外,它与使用dict完全相似。

    不幸的是, 数据库管理 有两个问题,但都是可以解决的:

    • 根据底层实现的不同,大型数据库可能无法工作,或者运行得非常缓慢。你可以使用一个现代的变体,比如KyotoCabinet来解决这个问题,但是你需要一个第三方包装器。
    • 数据库管理 键和值只能是 bytes 数据库管理 模块将内容包装起来,以允许透明地存储Unicode字符串,但不允许其他内容。但是Python附带了另一个模块, shelve

    但您可能希望使用更强大的键值数据库,如Dynamo或Couchbase。

    或者,您可以将远程MySQL中的数据转储到本地MySQL,甚至本地SQLite(还可以选择在其前面抛出ORM)。

        5
  •  0
  •   intellimath    7 年前

    借助于 recordclass :

    from recordclass import dataobject
    
    class User(dataobject):
        __fields__ = 'user_id', 'name', 'type'
    

    __slots__ 差异等于24字节(的大小 PyGC_Head