深入解析:NumPy数组与Python列表存储大小差异及优化策略

深入解析:NumPy数组与Python列表存储大小差异及优化策略

本文旨在探讨NumPy数组在特定场景下为何可能比等效的Python列表占用更多存储空间,并提供优化NumPy数组存储大小的方法。核心在于理解NumPy的原始数据存储方式与Pickle序列化Python列表时对共享对象引用的处理机制,并介绍使用numpy.savez_compressed进行数据压缩的实践。

1. NumPy数组的存储机制

numpy数组以连续的内存块存储数据,其大小直接由数组的形状(shape)和数据类型(dtype)决定,且默认情况下不进行压缩。这种设计旨在提供高效的数值计算性能,因为数据可以被快速访问和处理。

例如,一个形状为 (10000, 10000, 7) 且数据类型为 np.float16 的NumPy数组,其理论存储大小可以通过以下方式计算:

数组元素总数:10000 * 10000 * 7 = 700,000,000np.float16 占用内存:2 字节(16位)总存储大小:700,000,000 * 2 字节 = 1,400,000,000 字节 = 1.4 GB

这与观察到的1.4 GB大小完全吻合。NumPy在保存这类数组时,会直接将这些原始的二进制数据写入文件,因此文件大小与内存中的数据大小基本一致。

2. Python列表与Pickle的优化策略

与NumPy数组的原始存储不同,Python列表存储的是对对象的引用。当使用 pickle 模块序列化Python列表时,pickle 会智能地处理共享对象引用。如果列表中包含多个对同一个对象的引用,pickle 只会序列化该对象一次,然后在其他位置存储对该已序列化对象的引用。这可以显著减少文件大小,尤其是在数据存在大量重复引用时。

考虑以下场景:假设我们有一个 all_games 列表,其中包含多个7元素的浮点数子列表。当我们通过 random.choice(all_games) 构造一个大型的 sampled_data 列表时,sampled_data 中的每个元素实际上是对 all_games 中某个现有子列表的引用,而不是创建新的子列表对象。

示例代码:Python列表的采样与Pickle序列化

立即学习“Python免费学习笔记(深入)”;

import randomimport pickleimport sys# 假设 all_games 包含一些7元素的列表# 为了演示,我们创建一个简单的 all_gamesall_games = [[float(i), float(i+1), float(i+2), float(i+3), float(i+4), float(i+5), float(i+6)] for i in range(100)]def sample_and_pickle_list(all_games, file_name, DRAW=10000, SAMPLE=10000):    print(f"开始生成Python列表 (SAMPLE={SAMPLE}, DRAW={DRAW})...")    # sampled_data 存储的是对 all_games 中现有子列表的引用    sampled_data = [[random.choice(all_games) for _ in range(DRAW)] for _ in range(SAMPLE)]    print(f"生成的列表内存大小估算 (sys.getsizeof): {sys.getsizeof(sampled_data) / (1024**2):.2f} MB (仅顶层列表)")    with open(file_name, 'wb') as file:        pickle.dump(sampled_data, file)    print(f"Python列表已保存到 {file_name},文件大小可能远小于NumPy数组。")# 调用函数进行演示# sample_and_pickle_list(all_games, 'sampled_list.pkl')

在这种情况下,由于 sampled_data 中大量元素引用了 all_games 中相同的少量子列表对象,pickle 在序列化时只存储这些独特的子列表一次,然后记录它们的引用关系,从而导致最终的 .pkl 文件远小于NumPy数组的原始数据大小(例如,500 MB 对比 1.4 GB)。

关键验证:强制复制对象

如果我们在创建 sampled_data 时强制复制每个子列表,即 random.choice(all_games).copy(),那么 sampled_data 将包含大量独立的子列表对象。在这种情况下,pickle 将不得不序列化每一个独立的子列表,文件大小将急剧增加,甚至可能超过NumPy数组的大小(例如,达到4.4 GB)。这有力地证明了 pickle 对共享引用的优化作用。

3. 优化NumPy数组存储大小

当NumPy数组占用过多存储空间时,最直接有效的优化方法是使用NumPy自带的压缩功能。numpy.savez_compressed 函数可以将NumPy数组保存为 .npz 格式,并使用 zip 压缩算法对数据进行压缩。

示例代码:NumPy数组的采样与压缩保存

import numpy as npimport randomimport os# 假设 all_games 包含一些7元素的列表# 为了演示,我们创建一个简单的 all_gamesall_games_list = [[float(i), float(i+1), float(i+2), float(i+3), float(i+4), float(i+5), float(i+6)] for i in range(100)]# 将 all_games 转换为 NumPy 数组以便后续采样all_games_np = np.array(all_games_list, dtype=np.float16)def sample_and_save_numpy(all_games_source, file_name, DRAW=10000, SAMPLE=10000):    print(f"开始生成NumPy数组 (SAMPLE={SAMPLE}, DRAW={DRAW})...")    # 生成随机索引    rng = np.random.default_rng()    sampled_indices = rng.choice(all_games_source.shape[0], size=(SAMPLE, DRAW), replace=True)    # 根据索引获取采样数据    sampled_data_np = all_games_source[sampled_indices]    print(f"生成的NumPy数组形状: {sampled_data_np.shape}, dtype: {sampled_data_np.dtype}")    print(f"NumPy数组原始内存大小估算: {sampled_data_np.nbytes / (1024**3):.2f} GB")    # 1. 不压缩保存 (对应原始问题中的1.4GB情况)    uncompressed_file_name = file_name.replace('.npz', '_uncompressed.npy')    np.save(uncompressed_file_name, sampled_data_np)    print(f"NumPy数组未压缩保存到 {uncompressed_file_name},文件大小: {os.path.getsize(uncompressed_file_name) / (1024**3):.2f} GB")    # 2. 压缩保存    compressed_file_name = file_name    np.savez_compressed(compressed_file_name, data=sampled_data_np)    print(f"NumPy数组压缩保存到 {compressed_file_name},文件大小: {os.path.getsize(compressed_file_name) / (1024**2):.2f} MB")# 调用函数进行演示# sample_and_save_numpy(all_games_np, 'sampled_numpy_compressed.npz')

通过使用 np.savez_compressed,我们可以预期将1.4 GB的NumPy数组显著压缩,使其文件大小可能远小于原始的500 MB pickled列表,具体压缩率取决于数据的重复性和可压缩性。

4. 注意事项与最佳实践

理解数据结构与序列化机制: 在处理大量数据时,深入理解底层数据结构(NumPy数组的原始存储 vs. Python列表的对象引用)及其序列化方式(np.save 的直接写入 vs. pickle 的智能引用处理)至关重要。选择合适的工具对于需要高性能数值计算和连续内存访问的场景,NumPy是首选。对于包含复杂Python对象结构、且可能存在大量共享引用的数据,pickle 配合Python列表可能在文件大小上有优势(如果共享引用优化生效)。压缩与性能的权衡: numpy.savez_compressed 能够有效减小文件大小,但代价是保存和加载时需要额外的CPU时间进行压缩和解压缩。在对I/O性能要求极高的场景下,可能需要权衡是否使用压缩。数据类型优化: 即使不压缩,选择最小合适的数据类型(如 np.float16 而非 np.float64)也能显著减少NumPy数组的存储空间。

总结

NumPy数组与Python列表在存储方式上存在根本差异。NumPy数组默认以原始、未压缩的连续内存块存储数据,其文件大小直接反映数据量。而Python列表通过存储对象引用,结合 pickle 在序列化时对共享引用的优化,可以在数据存在大量重复引用时实现更小的文件大小。为了优化NumPy数组的存储空间,推荐使用 numpy.savez_compressed 进行数据压缩,这通常能有效减小文件体积,甚至使其小于等效的Python pickled列表。在实际应用中,理解这些机制并根据具体需求选择最合适的数据结构和存储方法是高效数据处理的关键。

以上就是深入解析:NumPy数组与Python列表存储大小差异及优化策略的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1375741.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月14日 15:17:16
下一篇 2025年12月14日 15:17:28

相关推荐

  • 如何在循环中向RandomForestRegressor传递超参数字典

    本文旨在解决在Python sklearn库中,当尝试通过循环将一个包含多个超参数的字典直接传递给RandomForestRegressor构造函数时遇到的常见InvalidParameterError。核心解决方案是利用Python的字典解包运算符**,将字典中的键值对转换为独立的关键字参数,从而…

    2025年12月14日
    000
  • 解决 Kivy BuilderException:理解 KV 文件重复加载机制

    本教程深入探讨 Kivy 应用中因 KV 文件重复加载导致的 BuilderException 错误,特别是当显式调用 Builder.load_file() 与 Kivy 的自动加载机制冲突时。文章将解释 Kivy 的加载原理,并提供两种解决方案:移除冗余的 Builder.load_file 调…

    2025年12月14日
    000
  • PyTorch中查找张量B元素在张量A中所有索引位置的内存优化方案

    本文探讨了PyTorch中高效查找张量B元素在张量A中所有索引位置的策略,尤其针对大规模张量避免广播内存限制。提供了结合部分广播与Python循环的混合方案,以及纯Python循环迭代方案,旨在优化内存并生成结构化索引。文章将指导开发者根据场景选择最佳方法。 引言:大规模张量索引查找的挑战 在pyt…

    2025年12月14日
    000
  • 解决 Pyfolio 与 Pandas 2.0+ 兼容性问题的教程

    本文旨在解决Pyfolio在Pandas 2.0.0及更高版本中出现的AttributeError: ‘Series’ object has no attribute ‘iteritems’和在旧版Pandas中可能遇到的IndexError问题。由于…

    2025年12月14日
    000
  • SQLAlchemy ORM 中 CTEs 列的访问与 aliased 的应用

    本文旨在深入探讨 SQLAlchemy ORM 中 CTEs(公共表表达式)的使用方式,特别是如何正确访问 CTEs 中的列以及 aliased 函数在不同场景下的应用。我们将澄清 CTEs 被视为“表”的这一核心概念,并提供示例代码,详细说明如何处理仅选择部分列的 CTEs,以及如何利用 alia…

    2025年12月14日
    000
  • Pygame角色移动教程:掌握位置管理与碰撞检测

    本教程深入探讨Pygame中角色移动的实现机制,重点介绍如何通过管理位置变量或使用pygame.Rect对象来控制角色在屏幕上的精确移动。文章将详细讲解事件处理、按键检测、帧率控制以及碰撞检测等核心概念,并提供清晰的代码示例和最佳实践,帮助开发者构建流畅、响应迅速的Pygame游戏。 理解Pygam…

    2025年12月14日
    000
  • SQLAlchemy 模型高效转换为 JSON:多方案深度解析

    本文深入探讨了在Python后端开发中,如何将复杂的SQLAlchemy模型(包括继承和关联字段)转换为JSON格式以供API响应。文章详细介绍了三种主流且现代的解决方案:SQLAlchemy-serializer、Pydantic以及SQLModel,并通过具体的代码示例展示了它们的实现方式、优势…

    2025年12月14日
    000
  • Python列表推导式中避免外部变量副作用的实践指南

    本文旨在深入探讨Python列表推导式中为何不能直接对外部变量进行增量操作,并提供一系列符合Pythonic风格的解决方案。我们将详细解释列表推导式作为表达式而非语句的本质,并通过具体示例演示如何利用sum()、len()以及优化数据生成过程来高效地实现计数或聚合功能,从而避免副作用并提升代码的清晰…

    2025年12月14日
    000
  • 应对大规模PDF标题提取:PyMuPDF与机器学习的局限及专业OCR工具的优势

    本文探讨了从大量、布局多变的PDF文档中提取标题的挑战,尤其是在元数据不可靠的情况下。尽管基于PyMuPDF提取特征并训练分类器的机器学习方法看似可行,但面对上百种布局时,其鲁棒性和维护成本极高。文章强烈建议,对于此类复杂场景,投资于具备模板定义、拖放式GUI和人工审核工作流的专业OCR系统,将是更…

    2025年12月14日
    000
  • Pygame角色移动教程:掌握位置管理与Rect对象

    在Pygame中实现角色移动,关键在于正确管理其屏幕位置。本文将详细介绍如何通过维护独立的坐标变量或更高效地利用pygame.Rect对象来控制角色移动,并结合事件处理、游戏循环优化及碰撞检测,构建流畅、响应式的游戏体验。 理解Pygame中的角色位置与移动原理 在pygame中,绘制(blit)一…

    2025年12月14日
    000
  • Python应用Docker化后模块导入错误的深度解析与解决方案

    本文深入探讨了Python应用在Docker容器中运行时,可能遇到的ModuleNotFoundError或ImportError问题。文章将分析Python的模块导入机制、Docker环境中的PYTHONPATH配置以及__init__.py的作用,并着重揭示一个常被忽视但至关重要的原因:源文件未…

    2025年12月14日
    000
  • Kivy BuilderException:理解并解决KV文件重复加载问题

    本文深入探讨了Kivy应用开发中因KV文件重复加载导致的BuilderException。当Kivy的App类自动加载与应用类名对应的KV文件时,若再通过Builder.load_file()显式加载同一文件,便会引发解析错误,尤其是在KV文件中定义了自定义属性时。解决方案是移除冗余的Builder…

    2025年12月14日
    000
  • Pygame角色移动:掌握坐标与Rect对象实现流畅控制

    在Pygame中,实现角色移动的关键在于正确管理其位置坐标。本文将详细介绍如何使用简单的X/Y变量或更强大的pygame.Rect对象来控制角色在屏幕上的移动,并探讨游戏循环、事件处理、帧率控制及碰撞检测等核心概念,助您构建响应式的Pygame游戏。 1. 理解Pygame中的角色位置管理 初学者在…

    2025年12月14日
    000
  • 高效将SQLAlchemy模型序列化为JSON的专业指南

    本文旨在为Python后端开发者提供将SQLAlchemy模型对象及其关联关系高效序列化为JSON格式的专业指南。针对传统方法难以处理继承字段和关联对象的问题,文章详细介绍了三种主流解决方案:SQLAlchemy-serializer、Pydantic以及SQLModel,并通过详细代码示例和解释,…

    2025年12月14日
    000
  • 高效将SQLAlchemy模型转换为JSON的策略与实践

    在构建Python后端API时,将SQLAlchemy ORM模型对象转换为JSON格式是常见的需求,尤其是在处理具有继承关系或复杂关联的模型时。本文将深入探讨三种现代且高效的方法:使用SQLAlchemy-serializer混入、Pydantic进行数据验证与序列化,以及SQLModel框架,帮…

    2025年12月14日
    000
  • Python Enum _missing_ 方法:实现灵活的输入映射与值获取

    本文深入探讨了 Python enum 模块中 _missing_ 方法的强大功能,展示如何利用它实现枚举成员的灵活输入映射。通过自定义 _missing_ 方法,开发者可以处理多种格式的外部输入(如 “true”、”false”、”Y&#…

    2025年12月14日
    000
  • Numpy数组与Python列表存储大小深度解析:优化与误区

    本文深入探讨了Numpy数组在文件存储时可能比等效Python列表更大的原因,打破了Numpy总是更节省内存的普遍认知。核心在于Numpy的np.save默认存储原始二进制数据不进行压缩,而Python的pickle机制在遇到重复对象时会存储引用而非副本,从而在特定场景下导致文件大小差异。文章提供了…

    2025年12月14日
    000
  • 大规模PDF文档标题提取:从自定义分类到智能OCR系统

    本文探讨了从包含多种布局且元数据不可靠的PDF文档中高效提取标题的挑战。面对20000份PDF和约100种不同布局,单纯基于字体大小的规则或自定义特征分类方法效率低下且难以维护。针对此类大规模、高复杂度的场景,文章推荐采用成熟的OCR系统结合可视化模板定义和人工复核流程,以实现更鲁棒、更可持续的标题…

    2025年12月14日
    000
  • Selenium自动化中处理Shadow DOM内元素的登录点击问题

    本文旨在解决Selenium自动化测试中,因目标元素位于Shadow DOM内部而导致的NoSuchElementException问题。我们将详细介绍如何通过浏览器开发者工具获取元素的JavaScript路径,并利用Selenium的execute_script方法,实现对Shadow DOM内部…

    2025年12月14日
    000
  • 优化Python中字符串列表前缀匹配的效率

    本文探讨了在Python中高效检查字符串列表是否包含以另一列表中的前缀开头的字符串的问题。针对原始的O(nk)双循环方法,文章介绍了使用正则表达式及其编译、以及trieregex库进行优化的策略。通过构建Trie树并生成精简的正则表达式,以及进一步移除冗余前缀,可以显著提升在大规模数据集上的匹配性能…

    2025年12月14日
    000

发表回复

登录后才能评论
关注微信