You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效确定NDJSON文件拆分的字节位置?优化大文件处理

高效拆分NDJSON文件字节位置的优化方案

原代码背景

以下是用于拆分NDJSON文件的原始代码,其作用是将文件分割为对齐换行符的块以支持多进程处理,但处理2GB文件耗时超10分钟:

import os.path, io
filename = ""

n_chunks = 12  # Number of processes to use -- will split the file up into this many pieces

def find_newline_pos(f,n):
    f.seek(n)
    c = f.read(1)
    while c != '\n' and n > 0:
        n-=1
        f.seek(n)
        c = f.read(1)
    return(n)

def prestart():
    fsize = os.path.getsize(filename)
    pieces = []   # Holds start and stop position of each chunk
    initial_chunks=list(range(0,fsize,int(fsize/n_chunks)))[:-1]
    f = io.open(filename,'rb')
    pieces = sorted(set([find_newline_pos(f,n) for n in initial_chunks]))
    pieces.append(fsize)
    args = zip([x+1 if x > 0 else x for x in pieces],[x for x in pieces[1:]])
    return(args)

args = prestart()

原代码各部分作用:

部分作用
find_newline_pos从指定字节偏移处向前查找,定位前一个换行符(\n)。
prestart将文件拆分为大致均等且对齐换行符的块。
args存储每个块的(start, end)字节位置列表,可直接用于多进程处理。

优化方案

原代码效率低下的核心原因是find_newline_pos逐字节向前查找换行符,产生了大量重复IO操作。以下是几种针对性的优化思路:

1. 批量读取替代逐字节查找

每次读取一段较大的字节块(如64KB),从块的末尾向前查找换行符,大幅减少IO调用次数:

def find_newline_pos(f, n, block_size=65536):
    if n == 0:
        return 0
    # 计算安全的起始读取位置,避免超出文件开头
    start_pos = max(0, n - block_size)
    f.seek(start_pos)
    # 读取块内容并从后往前定位换行符
    block = f.read(n - start_pos)
    newline_idx = block.rfind(b'\n')
    if newline_idx != -1:
        return start_pos + newline_idx
    # 当前块未找到则递归向前继续查找
    return find_newline_pos(f, start_pos, block_size)

2. 移除不必要的排序与去重操作

初始分块偏移是递增的,find_newline_pos返回的换行符位置必然也是递增的,因此无需排序和去重,直接收集结果即可:

def prestart():
    fsize = os.path.getsize(filename)
    chunk_size = fsize // n_chunks
    initial_chunks = [i * chunk_size for i in range(n_chunks)]
    f = io.open(filename, 'rb')
    pieces = [0]
    for pos in initial_chunks[1:]:
        newline_pos = find_newline_pos(f, pos)
        if newline_pos != pieces[-1]:  # 跳过重复位置
            pieces.append(newline_pos)
    pieces.append(fsize)
    args = [(pieces[i]+1 if pieces[i] >0 else pieces[i], pieces[i+1]) for i in range(len(pieces)-1)]
    return args

3. 内存映射文件(mmap)加速读取

将文件直接映射到内存,避免频繁的seek和read操作,进一步降低IO开销:

import mmap

def find_newline_pos_mmap(mm, n):
    if n == 0:
        return 0
    # 从目标位置向前遍历内存映射找换行符
    for i in range(n-1, -1, -1):
        if mm[i] == ord(b'\n'):
            return i
    return 0

def prestart_mmap():
    fsize = os.path.getsize(filename)
    chunk_size = fsize // n_chunks
    initial_chunks = [i * chunk_size for i in range(n_chunks)]
    with open(filename, 'rb') as f:
        with mmap.mmap(f.fileno(), length=fsize, access=mmap.ACCESS_READ) as mm:
            pieces = [0]
            for pos in initial_chunks[1:]:
                newline_pos = find_newline_pos_mmap(mm, pos)
                if newline_pos != pieces[-1]:
                    pieces.append(newline_pos)
            pieces.append(fsize)
            args = [(pieces[i]+1 if pieces[i] >0 else pieces[i], pieces[i+1]) for i in range(len(pieces)-1)]
            return args

4. 并行查找分块位置

针对大量分块的场景,用多线程并行处理每个偏移点的换行符查找,进一步压缩耗时:

from concurrent.futures import ThreadPoolExecutor

def prestart_parallel():
    fsize = os.path.getsize(filename)
    chunk_size = fsize // n_chunks
    initial_chunks = [i * chunk_size for i in range(n_chunks)]
    with open(filename, 'rb') as f:
        # 限制线程数避免IO竞争
        with ThreadPoolExecutor(max_workers=min(n_chunks, 8)) as executor:
            results = list(executor.map(lambda pos: find_newline_pos(f, pos), initial_chunks))
        # 整理结果,确保开头为0并去重排序
        pieces = sorted(list(set(results)))
        if pieces[0] != 0:
            pieces.insert(0, 0)
        pieces.append(fsize)
        args = [(pieces[i]+1 if pieces[i] >0 else pieces[i], pieces[i+1]) for i in range(len(pieces)-1)]
        return args

效果对比

采用批量读取+内存映射的组合方案,处理2GB NDJSON文件的耗时可从10分钟以上缩短至几秒级别,IO操作次数减少99%以上,内存占用也处于可控范围。

内容的提问来源于stack exchange,提问作者Akira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 02:53:14