Python数据转dict内存问题:如何高效加载数据到dict中?

0 投票
1 回答
28 浏览
提问于 2025-04-14 15:49

我正在尝试将一个非常大的数据集(大约560MB)加载到一个字典中,以便将其显示为3D图形。可是,我遇到了内存问题,导致程序被“杀掉”。为了解决这个问题,我添加了一些逻辑来分块读取数据集,并定期将字典保存到一个json文件中,我希望这样可以避免我的内存被填满。

不过,我还是在进度达到大约4.00M/558.0M时被杀掉了。

我想弄明白,为什么这个大约560MB的文件在处理时会消耗我几GB的内存,仅仅是为了去掉一些不需要的列并转换成字典?有没有更有效的方法来获取我需要的数据对象,这样我就可以高效地提取坐标和它们的值。

下面是我的代码和一些示例数据:

import json
import logging
import os

import pandas as pd
from tqdm import tqdm


def create_grid_dict(file_path, chunk_size=500000):
    """
    :param file_path: Path to a grid file.
    :param chunk_size: Number of lines to process before dumping into json
    :return: Dictionary object containing the gist grid data with as index the voxel number
             and as values the x, y and z coordinates, and the value
    """
    # Read the data from the file
    with open(file_path, 'r') as file:
        # Read the first line
        header = file.readline().strip()
        header2 = file.readline().strip()
        # Log the header
        logging.info(header)
    columns = header2.split(' ')

    # Get the file size
    file_size = os.path.getsize(file_path)

    output_file = 'datasets/cache.json'
    # Check if the output file already exists
    if os.path.exists(output_file):
        with open(output_file, 'r') as f:
            grid_dict = json.load(f)
            return grid_dict
    else:
        # Create an empty dictionary to store the grid data
        grid_dict = {}

    logging.info(f"Reading file size {file_size} in chunks of {chunk_size} lines.")
    # Read the file in chunks
    with tqdm(total=file_size, unit='B', unit_scale=True, desc="Processing") as pbar:
        for chunk in pd.read_csv(file_path, delim_whitespace=True, skiprows=2, names=columns, chunksize=chunk_size):
            # Filter out the columns you need
            chunk = chunk[['voxel', 'xcoord', 'ycoord', 'zcoord', 'val1', 'val2']]

            # Iterate through each row in the chunk
            for index, row in chunk.iterrows():
                voxel = row['voxel']
                # Store the values in the dictionary
                grid_dict[voxel] = {
                    'xcoord': row['xcoord'],
                    'ycoord': row['ycoord'],
                    'zcoord': row['zcoord'],
                    'val': row['val1'] + 2 * row['val2']
                }
            pbar.update(chunk_size)

            # Write the grid dictionary to the output file after processing each chunk
            with open(output_file, 'w') as f:
                json.dump(grid_dict, f)
    return grid_dict
# Example space-delimited dataset
voxel xcoord ycoord zcoord val1 val2
1 0.1 0.2 0.3 10 5
2 0.2 0.3 0.4 8 4
3 0.3 0.4 0.5 12 6
4 0.4 0.5 0.6 15 7
5 0.5 0.6 0.7 9 3
6 0.6 0.7 0.8 11 5
7 0.7 0.8 0.9 13 6
8 0.8 0.9 1.0 14 7
9 0.9 1.0 1.1 16 8
10 1.0 1.1 1.2 18 9

1 个回答

1

你不是在创建一个普通的 dict,而是在创建一个包含多个 dictdict,每一个小的 dict 都会占用不少内存(在我的电脑上,每个小 dict 自己就会增加 184 字节的内存开销)。你这样分块的做法几乎没什么用,因为这个包含多个 dictdict 会不断增大,而那个(可能存储得相当高效的)数据框在内存上可能并不是那么重要。

为了减少这种内存开销,你可以尝试让你的 dict 映射到一种叫做 slotted classes 的东西,而不是普通的 dict,然后使用 dataclasses.asdict 来把它们转换成 dict 的形式:

import dataclasses
import json

@dataclasses.dataclass(slots=True)  # slots=True optimizes to store only the declared attributes with less memory
class Voxel:
    xcoord: float
    ycoord: float
    zcoord: float
    val: int

然后把你循环中相关的部分改成:

            for index, row in chunk.iterrows():
                voxel = row['voxel']
                # Store the values in the dict
                grid_dict[voxel] = Voxel(row['xcoord'],
                                         row['ycoord'],
                                         row['zcoord'],
                                         row['val1'] + 2 * row['val2']
                                        )
            pbar.update(chunk_size)

            # Write the grid dictionary to the output file after processing each chunk
            with open(output_file, 'w') as f:
                json.dump(grid_dict, f, default=dataclasses.asdict)  # Converts to dict lazily just in time to convert it to a string, rather than all at once

dataclasses 让自定义类的声明变得更快,并且提供了 dataclasses.asdict 这个工具,使得序列化(把数据转换成可以存储或传输的格式)变得简单(虽然 collections.namedtupletyping.NamedTuple 看起来也能用,但不幸的是,它们会被当作普通的 tuple 处理,并以 JSON 数组的形式序列化)。

在我本地的 Python 环境中,这样做的净节省是每个 Voxel 120 字节(每个 dict 的开销是 184 字节,而每个 Voxel 实例的开销是 64 字节)。如果你的内存快满了,这可能足够让你腾出空间。

撰写回答