V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
wcsjtu
V2EX  ›  Python

请问大家, 有没有办法把 Python 的 dict, 存储到一段连续内存上

  •  1
     
  •   wcsjtu · 2022-07-25 15:08:32 +08:00 · 6404 次点击
    这是一个创建于 850 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比如说, 我这里有个

    table: Dict[str, List[str]] = {
    	"key1": ["value1", "value2", ...],
        ...,
        "keyn": ["valuem", ...]
    }
    

    我想把这个 dict 中的所有对象(包括所有的 key, value, 以及 list), 全部存储到一段连续内存里, 同时还能支持查表操作, 比如说

    values: List[str] = table_in_contigious_memory["key1"]
    

    有没有现成的轮子可以做这种事啊。

    PS: 可以不是 Python 原生的 Dict, 只要是 dict-like, 支持查表就行.....

    85 条回复    2022-08-20 22:32:40 +08:00
    liprais
        1
    liprais  
       2022-07-25 15:18:46 +08:00
    自己写个 c lib
    不过为啥你要在 python 里面干这种事
    filwaline
        2
    filwaline  
       2022-07-25 15:22:02 +08:00   ❤️ 1
    这什么莫名其妙的要求? Python 这种高级语言压根不关心内存分配,你为什么要这么做?
    LeegoYih
        3
    LeegoYih  
       2022-07-25 15:23:55 +08:00   ❤️ 8
    晒干了沉默
    wcsjtu
        4
    wcsjtu  
    OP
       2022-07-25 15:28:15 +08:00
    @liprais 因为词表很大,要把它塞到共享内存里......
    wcsjtu
        5
    wcsjtu  
    OP
       2022-07-25 15:30:25 +08:00   ❤️ 1
    @filwaline 确实很罕见的要求, 但是没有别的办法了。Python 有办法把 dict 塞到共享内存里么?
    liprais
        6
    liprais  
       2022-07-25 15:31:26 +08:00
    @wcsjtu 开个 mmap ?
    wcsjtu
        7
    wcsjtu  
    OP
       2022-07-25 15:35:47 +08:00
    @liprais 嗯, 但是怎么放进去呢,Python 没有 placement new......
    FYFX
        8
    FYFX  
       2022-07-25 15:38:00 +08:00
    为什么非得用 python 做,你找个 redis 之类的 KV 存储的塞进去不行吗
    qwq11
        9
    qwq11  
       2022-07-25 15:41:06 +08:00 via Android
    cpy 自己写一个吧,直接 struct 胡上去代码量应该不超过百行
    filwaline
        10
    filwaline  
       2022-07-25 15:45:23 +08:00
    @FYFX 我也觉得其实楼主需要的是 redis 这样的 kv 存储
    ipwx
        11
    ipwx  
       2022-07-25 15:51:10 +08:00
    wcsjtu
        12
    wcsjtu  
    OP
       2022-07-25 15:57:05 +08:00
    @filwaline
    @FYFX

    用 redis 的话,性能太差了,dict 一次查表, 大概是 10ns 左右, 用 redis 的话, 至少是 us 级别。
    ysc3839
        13
    ysc3839  
       2022-07-25 15:58:37 +08:00
    如果是多个程序共用一个 dict 的话,还是建议用 redis 等独立的服务。自己写的话费时费力还可能有 bug 。
    wcsjtu
        14
    wcsjtu  
    OP
       2022-07-25 16:02:15 +08:00
    @ipwx 嗯, 这种半内存方案, 我们也尝试过, 但是对性能影响比较大。 目前还是希望有一个不需要序列化的方案,貌似就只能用共享内存了
    wcsjtu
        15
    wcsjtu  
    OP
       2022-07-25 16:03:31 +08:00
    @qwq11 我觉得自己写很难啊.......
    lysS
        16
    lysS  
       2022-07-25 16:08:43 +08:00
    为什么认为连续内存时,占用就会小?
    zyx199199
        17
    zyx199199  
       2022-07-25 16:10:25 +08:00   ❤️ 1
    需要的是类似 https://github.com/luizalabs/shared-memory-dict 这种?

    或者是 multiprocessing 库里的 Manager ?类似这种 https://stackoverflow.com/questions/6832554/multiprocessing-how-do-i-share-a-dict-among-multiple-processes

    以上两个都是谷歌搜索 python shared memory dict 找到的。楼主可以试试
    ipwx
        18
    ipwx  
       2022-07-25 16:14:51 +08:00
    @wcsjtu 不能理解你的需求。。。。感觉 pandas.DataFrame 基本可以处理大部分情况了,你的数据内存放不下么。。
    ipwx
        19
    ipwx  
       2022-07-25 16:16:56 +08:00   ❤️ 2
    @wcsjtu 另外如果你是 Linux 或 mac ,如果你的大表在创建以后就不修改了,那你可以用 os.fork ,直接共享 fork 前建立完成的全局变量。mac python 默认禁用了 fork 得设个环境变量。

    ----

    这种操作的原理基于一个事实:Linux / mac 的内核进程拷贝是按 4K page 进行 copy-on-write 的。
    wzaqqq
        20
    wzaqqq  
       2022-07-25 16:19:50 +08:00
    /dev/shm 不知道能不能搞
    daxin945
        21
    daxin945  
       2022-07-25 16:20:52 +08:00
    lmdb ?
    liuxingdeyu
        22
    liuxingdeyu  
       2022-07-25 16:22:24 +08:00
    为啥要连续内存的 dict
    wcsjtu
        23
    wcsjtu  
    OP
       2022-07-25 16:38:39 +08:00
    @lysS 因为可以扔到共享内存里, 多个进程可以共享一份词表。
    wcsjtu
        24
    wcsjtu  
    OP
       2022-07-25 16:42:06 +08:00
    @ipwx 确实是是内存放不下, 单个表大概占 3G 的内存。 这个表确实是只读的。我之前用的就是 fork , 但是 fork 不管用, 对于 Python 来说,由于每次 read 都会改引用计数,所以是 copy-on-read .....
    wcsjtu
        25
    wcsjtu  
    OP
       2022-07-25 16:45:08 +08:00
    @zyx199199 我还没深入去了解这两个库, 但是根据我理解,这两个库,都是要序列化的......性能不行啊
    ipwx
        26
    ipwx  
       2022-07-25 16:45:33 +08:00
    @wcsjtu 在我的经验中不是这样的啊,os.fork 我确实用的很舒服。

    要么你试试 numpy.ndarray 能不能 fork 共享。
    wcsjtu
        27
    wcsjtu  
    OP
       2022-07-25 16:46:35 +08:00
    @ipwx np.ndarray 是可以的, 但是有两个问题

    - np 存字符串太浪费内存了......
    - np 没办法存 dict
    TimePPT
        28
    TimePPT  
       2022-07-25 17:18:09 +08:00
    你这是基于啥需求啊……
    wcsjtu
        29
    wcsjtu  
    OP
       2022-07-25 17:40:00 +08:00
    @TimePPT 想把 Python 的大 dict 存到共享内存里, 节约内存。。。。。
    786375312123
        31
    786375312123  
       2022-07-25 17:41:46 +08:00   ❤️ 2
    @wcsjtu 为了节约内存你用 python ?你是不是队 python 有什么误解?
    maggch97
        32
    maggch97  
       2022-07-25 17:41:56 +08:00 via Android   ❤️ 9
    我发现很多奇葩需求,原因都是开发人员只懂 a ,导致有了问题 b ,最终想用一个非常奇葩的 c 方法来解决。
    maggch97
        33
    maggch97  
       2022-07-25 17:48:12 +08:00 via Android   ❤️ 1
    你都用 dict 了,逻辑必然不复杂,全都用 c++写这段逻辑不行吗,最终把封装好的查询接口暴露出去。

    看到你上面在纠结序列化速度的问题,除非你暴露给用户的接口就是裸的 dict 查询,否则返回结果的那点序列化时间根本无足轻重。如果是暴露 dict 接口,那查询时间本来就无所谓了,你反正都要走网络或者 rpc 等等协议
    listenerri
        34
    listenerri  
       2022-07-25 17:53:32 +08:00
    要不试试 zeromq 和它所支持的 ipc 进程间通讯,这样 dict 不用暴露给其他进程,本机跨进程通讯所传输的数据几乎只有一个字符串 key ,想来效率还是可以的
    VYSE
        35
    VYSE  
       2022-07-25 17:59:01 +08:00
    DonDonc
        36
    DonDonc  
       2022-07-25 18:00:31 +08:00
    listenerri
        37
    listenerri  
       2022-07-25 18:06:04 +08:00
    @VYSE #35 楼主期望避免序列化
    Muniesa
        38
    Muniesa  
       2022-07-25 18:07:57 +08:00 via Android   ❤️ 1
    numpy 的 Structured arrays ?
    xuelu520
        39
    xuelu520  
       2022-07-25 18:16:07 +08:00
    那有没有一种可能,现在内存这么便宜,直接加内存呢?
    sivacohan
        40
    sivacohan  
       2022-07-25 18:19:59 +08:00
    sqlite memory 表?
    ipwx
        41
    ipwx  
       2022-07-25 18:24:05 +08:00   ❤️ 1
    楼主能不能给个痛快,把最初的需求说一下。
    SenLief
        42
    SenLief  
       2022-07-25 18:32:33 +08:00 via iPhone
    不可能不序列化吧?内存它又不认识 dict ,那你写入内存的时候不需要利用 struct 或者 pickle 把数据转为字节再写到内存吗?
    SenLief
        43
    SenLief  
       2022-07-25 18:34:59 +08:00 via iPhone
    python 3.8 提供了 shared memory 来操作共享内存,我觉得可以通过 struct 或者 cpickle 转为字节后写入内存,效率应该是利用 python 最高的吧。
    lambdaq
        44
    lambdaq  
       2022-07-25 18:42:33 +08:00   ❤️ 1
    1. 感觉你想要的是 named tuple
    2. 研究下 __slots__
    3. 遇事不决 pandas
    wcsjtu
        45
    wcsjtu  
    OP
       2022-07-25 18:54:37 +08:00
    @ipwx
    @786375312123
    @maggch97

    我这边是个 NLP 的服务, 只能用 Python 跑…… 而且有个很大的词表, 需要加载到各个进程里。 想在就是想办法节约内存, 同时还得保证性能
    wcsjtu
        46
    wcsjtu  
    OP
       2022-07-25 18:56:06 +08:00
    @Muniesa 这个有意思, 我去学习一下
    wcsjtu
        47
    wcsjtu  
    OP
       2022-07-25 18:58:56 +08:00
    @SenLief 目前是想能不序列化就不序列化。 其实是可以在共享内存里存裸数据的,read 的时候加上 Python header 就行了。不过这个方法说起来简单, 但是做起来很麻烦
    wcsjtu
        48
    wcsjtu  
    OP
       2022-07-25 18:59:52 +08:00
    @lambdaq tuple 也不是存在连续内存里的, 里面各种 PyObject* ....
    786375312123
        49
    786375312123  
       2022-07-25 19:16:26 +08:00
    @wcsjtu 多花点钱找个 c++开发吧。python 只是做验证的
    wangyzj
        50
    wangyzj  
       2022-07-25 19:32:55 +08:00
    既然追求快
    为啥要 py ?
    wcsjtu
        51
    wcsjtu  
    OP
       2022-07-25 19:35:04 +08:00
    @wangyzj
    @786375312123
    历史原因, 只能用 Python……
    SenLief
        52
    SenLief  
       2022-07-25 19:50:23 +08:00 via iPhone
    @wcsjtu 共享内存能创建对象的吗?
    786375312123
        53
    786375312123  
       2022-07-25 19:57:46 +08:00
    @wcsjtu 不存在历史原因,对于内存有限制的情况下,使用 python ,做的还是 nlp 这种事情,闻所未闻。你们公司技术的头可能对计算机基础有什么误解。
    gengchun
        54
    gengchun  
       2022-07-25 20:05:57 +08:00   ❤️ 1
    要求性能比 Redis 高估计没有其它办法了。

    共享内存思路应该是没有错的,基于 shared memory 的 dict-like 实现其实有不少,不过都处于早期阶段。自己实现一下也没有太大关系。差不多都是自己实现的。
    lysS
        55
    lysS  
       2022-07-25 20:37:18 +08:00
    共享内存考虑了并发安全了吗。。。。
    wcsjtu
        56
    wcsjtu  
    OP
       2022-07-25 21:17:48 +08:00
    @lysS 因为如果能全部放到 shared memory 里,那肯定是只读的, 所以应该没有并发安全问题
    wcsjtu
        57
    wcsjtu  
    OP
       2022-07-25 21:19:25 +08:00
    @gengchun 嗯, 性能是一方面, 其实我们业务里还有很多这种词表, 有的结构比较复杂, 所以存 redis 不太合适。
    wcsjtu
        58
    wcsjtu  
    OP
       2022-07-25 21:20:09 +08:00
    @SenLief 应该不能,PyMalloc 是调用的 glibc 的 malloc 。应该要用 C++的轮子
    whenov
        59
    whenov  
       2022-07-25 21:25:28 +08:00   ❤️ 1
    pybind11 了解一下,在 C++里新建一个 Dictionary 类,给 Python 留个读取接口就行了
    ipwx
        60
    ipwx  
       2022-07-25 21:41:58 +08:00   ❤️ 1
    @wcsjtu 给你提供一个解决方案:

    把查表过程用 C++ 单独做个微服务,然后直接开多线程解决。Python 把文本提交给 C++ 服务,查完表弄到向量再调用 AI 的东西。
    air8712
        61
    air8712  
       2022-07-25 23:40:38 +08:00
    一台机器,大 dict ,想节省内存。起一个进程加载 dict ,提供查询服务,其他进程找这个进程要数据即可。至于怎么要,就是跨进程通信的问题了。
    sdshiyan2005
        62
    sdshiyan2005  
       2022-07-25 23:52:58 +08:00   ❤️ 1
    不知道 Arrow 项目里是不是有一部分能满足你需求: https://arrow.apache.org/use_cases/
    jeeyong
        63
    jeeyong  
       2022-07-25 23:55:36 +08:00 via iPhone
    盲猜一种做法
    数组里包含字典,不知道是否可行。
    或者有人能告知如何确定一个字典是否存储在一段连续的内存地址中?
    通过获取字典元素的内存地址可行吗?
    zhoujinjing09
        64
    zhoujinjing09  
       2022-07-26 01:38:18 +08:00
    只读的吗?需要同时读写吗?
    zhoujinjing09
        65
    zhoujinjing09  
       2022-07-26 01:48:26 +08:00   ❤️ 2
    根据我的经验,做到最后相比 socket 跨进程通讯性能优势很有限,你就 C++写个裸的 kv+socket 的通讯服务试试,单机通讯有 fast path ,代价应该是仅仅略高于 memcpy 。建议先说一下 QPS 的要求,共享内存是个很烦的事情,如果跨进程通讯稿代价不是瓶颈的话,性能提升很有限。
    mayli
        66
    mayli  
       2022-07-26 02:31:36 +08:00
    @wcsjtu 是只读的吗?如果是只读的,那就先创建这个表,然后 fork 就完事了
    neoblackcap
        67
    neoblackcap  
       2022-07-26 03:54:01 +08:00
    Python 又不能有效多线程利用内存,你这个如果是跨进程访问,内存跟性能省不了多少。
    如果是 nlp 的话,底层都是 C++/C 那些库。你自己封装就不知道好不好与其他组件兼容。
    至于你的需求应该是可以随机访问的哈希表,但是要求这个哈希表的内存布局是连续的。连续是没问题,不过连续的话,那么肯定要有额外的空间记录偏移,否则没法让你快速读取。
    capnproto 应该是能比较好完成你的需求,否则不用第三方库,你就需要自己定义一个哈希表,以及对应的内存表达格式。
    话说回来,C/C++生成一个 Python 的对象其实也是一种性能消耗
    wcsjtu
        68
    wcsjtu  
    OP
       2022-07-26 10:21:18 +08:00
    @ipwx
    @air8712
    @zhoujinjing09
    嗯, 目前把 IPC 作为最后兜底方案……
    wcsjtu
        69
    wcsjtu  
    OP
       2022-07-26 10:23:15 +08:00
    @mayli 嗯, 这个就是我必须要求连续内存的原因了。 词表本身只读, 但是有可能词表中的元素与某个对象在同一个 page 上, 如果这个对象被改了, 那么词表中的元素也会被 copy 。
    cassidyhere
        70
    cassidyhere  
       2022-07-26 10:38:19 +08:00
    可以试试 apache arrow 的 plasma ,底层是 c++写的 In-Memory Object Store ,api 很友好:
    import pyarrow.plasma as plasma
    client = plasma.connect("plasma")
    object_id = client.put({"a": 1})
    client.get(object_id)
    >>>{'a': 1}
    wcsjtu
        71
    wcsjtu  
    OP
       2022-07-26 10:44:40 +08:00
    @neoblackcap capnproto 好像是个序列化的库,dict 被序列化后怎么索引呢?

    这个事情如果要自己造轮子吧, 肯定要自己设计 str/int/float/list/dict 这些数据结构的, 然后还要定义他们转成 PyObject*的规则,很麻烦……

    所以我想问问大家有没有现成的轮子可以用用
    ipwx
        72
    ipwx  
       2022-07-26 11:17:38 +08:00
    @wcsjtu “ 如果这个对象被改了, 那么词表中的元素也会被 copy ”

    那你这个写法稍微有点问题。从共享内存用对象应该拷出来。
    ipwx
        73
    ipwx  
       2022-07-26 11:18:32 +08:00
    @wcsjtu “所以我想问问大家有没有现成的轮子可以用用”
    ----

    看起来你不是很熟悉操作系统、编译原理、计算机组成之类的。你这一楼的需求我觉得是不可实现的,只有 C++ Microservice 还算靠谱。
    ykk
        74
    ykk  
       2022-07-26 11:31:01 +08:00
    试试 sharedmemory
    laqow
        75
    laqow  
       2022-07-26 11:31:43 +08:00
    跨进程实质都是 socket 吧,序列化逃不掉的。搞个 sqlite 把数据放硬盘不行吗,不想大量改代码就照着 dict 操作函数重新封装一下。另外如果 value 大部分都是整数或空值的话可以用稀疏矩阵算法压缩吧。
    ykk
        76
    ykk  
       2022-07-26 11:32:28 +08:00
    @ykk #74 看到了 楼主不想序列化
    wcsjtu
        77
    wcsjtu  
    OP
       2022-07-26 11:37:36 +08:00
    @ipwx 这个是可以做的。 就是麻烦点。 其实有点像 msgpack 这种, 只不过它没办法索引。 需要自己改造, 在 msgpack 中加上地址的元信息, 给索引用。 最好是 str/int/... 这些类型都按照 Python 的格式存储, 只是不要 PyObjectHeader 。这样读表时, 就只需要新建 header ,然后再引用表中的数据即可。 原理就是这样, 但是很麻烦,不想自己搞
    echoechoin
        78
    echoechoin  
       2022-07-26 11:43:57 +08:00
    直接写一个 c 语言的类 dict 库,打包成动态库,用 python 调用呗
    lolizeppelin
        79
    lolizeppelin  
       2022-07-26 11:44:55 +08:00
    那就 struct.pack 当 c 写被,定义 fmt 就好
    crayygy
        80
    crayygy  
       2022-07-26 11:49:27 +08:00
    创建一个 dict 来 map key 和 index ,然后再用一个数组来存储 data 这样?
    ipwx
        81
    ipwx  
       2022-07-26 12:01:00 +08:00
    @wcsjtu 麻烦点当然能做,boost::ipc 还有 offset_ptr 呢。

    我说的是不想自己撸找轮子,不太可能实现。因为要实现通用的这种需求的框架太 tm 困难且没有必要了。
    changz
        82
    changz  
       2022-07-26 12:25:38 +08:00 via Android
    换树结构试试
    dayeye2006199
        83
    dayeye2006199  
       2022-07-26 14:02:22 +08:00 via Android
    Sqlite in memory database
    neoblackcap
        84
    neoblackcap  
       2022-07-26 14:49:44 +08:00   ❤️ 1
    @wcsjtu 是序列化,但是它的序列化是特化的,反序列化的成本接近于零。但是我也没有具体试过。
    然后如果你的需求还要读写的话,那么这个库就不能满足你的需求了。如果你需求还隐含跨进程访问。我还是建议你使用数据库。
    mattx
        85
    mattx  
       2022-08-20 22:32:40 +08:00   ❤️ 1
    https://github.com/KeyviDev/keyvi 看起来挺适合群主的需求
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3028 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 37ms · UTC 13:29 · PVG 21:29 · LAX 05:29 · JFK 08:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.