Python 如何使用4M+;带字符串的对象?

Python 如何使用4M+;带字符串的对象?,python,python-3.x,Python,Python 3.x,如何减少包含4M+字符串对象的字典的内存占用 它目前消耗大约1.5 GB的RAM,我需要在资源有限的系统上添加数百万个对象,因为成本太高(基于云计算) 下面是一些简单的代码,说明了我正在做的事情的要点。基本上,我从一个数据库中获取一组大约400万用户,并将所有信息放入一个本地dict中,所有用户都可以快速访问(出于性能原因,我必须使用用户数据的本地副本) 简化代码 数据类型 用户id:int 名称:字符串,每个字符串平均大约13个字符 类型:int 在某些web搜索中,由于字符串对象需要大量

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

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

下面是一些简单的代码,说明了我正在做的事情的要点。基本上,我从一个数据库中获取一组大约400万用户,并将所有信息放入一个本地dict中,所有用户都可以快速访问(出于性能原因,我必须使用用户数据的本地副本)

简化代码 数据类型
  • 用户id:int
  • 名称:字符串,每个字符串平均大约13个字符
  • 类型:int
在某些web搜索中,由于字符串对象需要大量内存,User.name似乎占用了大部分空间


我已经通过使用
\uuuu slots\uuuu
将占用空间从大约2GB减少到了1.5GB,但我需要进一步减少它。

而不是使用
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]
    ...

根据您想对所有用户执行的操作,您可能也不需要将所有用户信息存储在dict中。如果您可以逐个处理每个用户,请直接在上面的
for
循环中执行,而不是建立一个庞大的目录。

而不是使用
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]
    ...

根据您想对所有用户执行的操作,您可能也不需要将所有用户信息存储在dict中。如果你可以逐个处理每个用户,直接在<<代码>内为循环,而不是建立一个巨大的DITC.

< P>如果你真的需要本地数据,考虑把它保存到主机上的SQLite DB,让SQLite为你加载热数据集,而不是把它保存在内存中。
db_conn = sqlite3.connect(path_to_sqlite_file)
db_conn.execute('PRAGMA mmap_size={};'.format(mmap_size))

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

当然,如果
name
是一个大字符串,那么可以使用gzip压缩字符串。如果您的名字中有重复的单词,其他技巧包括使用索引进行重复数据消除

也可以使用结构而不是类

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数组在这里很有用)

对于更接近生产质量的内容,您需要有一个数据准备步骤,将这些结构附加到一个文件中,稍后在实际使用数据时可以读取该文件

# 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)
然而,我不相信这是解决实际问题的最佳设计。其他备选方案包括:

  • 检查数据库设计,尤其是索引
  • 使用memcache/redis缓存最常用的记录

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

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

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

当然,如果
name
是一个大字符串,那么可以使用gzip压缩字符串。如果您的名字中有重复的单词,其他技巧包括使用索引进行重复数据消除

也可以使用结构而不是类

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数组在这里很有用)

对于更接近生产质量的内容,您需要有一个数据准备步骤,将这些结构附加到一个文件中,稍后在实际使用数据时可以读取该文件

# 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)
然而,我不相信这是解决实际问题的最佳设计。其他备选方案包括:

  • 检查数据库设计,尤其是索引
  • 使用memcache/redis缓存最常用的记录

一个13个字符的字符串的实际字符串存储只需要13个字节(如果它全部是拉丁文1),26个字节(如果它全部是BMP),52个字节(如果它包含来自整个Unicode的字符)

但是,
str
对象的开销是另外52个字节。所以,假设你大部分都是拉丁语-1,那么你使用的存储空间大约是你需要的5倍


如果字符串一旦编码为UTF-8或UTF-16-LE或任何最适合您的数据的格式,大小大致相同,您可能希望将其存储在一个大的平面阵列中,并根据需要将其取出并动态解码,如中所示。尽管我可能会使用NumPy原生结构化数据类型,而不是使用
struct
模块

但是,如果您有几个巨大的字符串,并且您不想在大多数字符串只有10字节长的情况下为每个字符串浪费88字节,该怎么办

然后你需要一个字符串表。这只是一个巨大的
bytearray
,所有(编码的)字符串都存在于此,您将索引存储到该表中,而不是存储字符串本身。这些索引只是
int32
或最糟糕的
int64
值,您可以将它们打包到一个数组中而不会出现任何问题

例如,假设所有字符串都不超过255个字符,我们可以将它们存储为“Pascal字符串”,长度为字节