วิธีประมวลผลไฟล์ JSON ขนาดใหญ่ใน Python ด้วย ijson

JSON เป็นรูปแบบยอดนิยมสำหรับการจัดเก็บและแลกเปลี่ยนข้อมูลใน Python สำหรับไฟล์ขนาดเล็ก module json ที่มาพร้อมกับ Python นั้นทำงานได้ดีเยี่ยม แต่ปัญหาจะเริ่มเกิดขึ้นเมื่อไฟล์มีขนาดใหญ่ขึ้น
ฟังก์ชัน json.load() จะประมวลผล (parse) เอกสาร JSON ทั้งหมดและสร้าง Python object ขึ้นในหน่วยความจำก่อนเริ่มใช้งาน ไฟล์ที่มีขนาดเพียงไม่กี่ร้อยเมกะไบต์บนดิสก์จึงอาจใช้หน่วยความจำพุ่งสูงขึ้นมากเมื่อถูกแปลงเป็น dictionaries, lists, strings และ numbers
ทางเลือกที่มีประสิทธิภาพกว่าคือ ijson ซึ่งเป็น iterative JSON parser ที่ช่วยให้คุณประมวลผลข้อมูลแบบทีละส่วน (incrementally) ในบทความนี้เราจะใช้ไฟล์ JSON ข้อมูลแปลงที่ดินใน San Francisco เพื่อเปรียบเทียบการทำงานระหว่าง json.load() และ ijson ในแง่การใช้ทรัพยากรระบบ
Step 1: Downloading a Large JSON Dataset
เราจะใช้ชุดข้อมูล San Francisco City Lots ในการทดลอง โดยดาวน์โหลดผ่าน Google Colab ด้วยคำสั่งดังนี้:
curl -L \
https://raw.githubusercontent.com/zemirco/sf-city-lots-json/master/citylots.json \
-o citylots.jsonคุณสามารถตรวจสอบขนาดไฟล์ด้วยคำสั่ง:
ls -lh citylots.jsonไฟล์นี้มีขนาด 181 MB ซึ่งใหญ่พอจะเห็นความแตกต่างระหว่างการโหลดปกติกับการทำ streaming โดยโครงสร้างเป็นไปตามมาตรฐาน GeoJSON ซึ่งประกอบด้วย array ชื่อ features ที่เก็บข้อมูลพิกัดและคุณสมบัติของที่ดินแต่ละแปลง
Step 2: Processing the File With json.load()
มาเริ่มด้วยวิธีที่นักพัฒนาส่วนใหญ่คุ้นเคยผ่านไฟล์ standard_json.py เพื่อกรองหาที่ดินบนถนน "MARKET":
import json
import time
start = time.perf_counter()
with open("citylots.json", "r") as f:
data = json.load(f)
features = data["features"]
market_count = 0
first_five = []
for feature in features:
properties = feature["properties"]
if properties.get("STREET") == "MARKET":
market_count += 1
if len(first_five) < 5:
first_five.append(properties.get("BLKLOT"))
elapsed = time.perf_counter() - start
print(f"Total parcels: {len(features):,}")
print(f"Market Street parcels: {market_count:,}")
print(f"First five parcel IDs: {first_five}")
print(f"Time: {elapsed:.2f} seconds")แม้โค้ดจะถูกต้อง แต่ปัญหาอยู่ที่ json.load(f) เพราะ Python ต้องอ่านและ Parse ข้อมูลทั้งหมดรวมถึงพิกัดเรขาคณิต (coordinates) ที่เราไม่ได้ใช้เข้ามาเก็บไว้ในหน่วยความจำก่อนที่จะเริ่ม loop วิเคราะห์ข้อมูลได้
Step 3: Measuring the Memory Usage
เราจะวัดผลการใช้หน่วยความจำจริงด้วย memory-profiler:
pip install memory-profiler
python -m memory_profiler standard_json.pyผลลัพธ์แสดงให้เห็นว่าในบรรทัดที่ใช้ json.load(f) หน่วยความจำพุ่งสูงขึ้นอย่างรวดเร็ว:
Line # Mem usage Increment Occurrences Line Contents
=============================================================
9 808.9 MiB 765.5 MiB 1 data = json.load(f)สาเหตุที่หน่วยความจำสูงกว่าขนาดไฟล์บนดิสก์หลายเท่า เพราะ JSON object ขนาดเล็กเมื่อถูกแปลงเป็น Python objects จะต้องใช้โครงสร้างข้อมูลที่ซับซ้อนกว่าเดิม ทั้งในส่วนของ string keys, values และ nested arrays
Step 4: Streaming the JSON File With ijson เพื่อแก้ปัญหาข้างต้น เราจะใช้ ijson เพื่อดึงข้อมูลทีละ record โดยไม่ต้องโหลดไฟล์ทั้งหมด:
pip install ijsonฟังก์ชันหลักคือ ijson.items() ซึ่งเราสามารถกำหนดเส้นทาง (prefix) เข้าไปในโครงสร้าง JSON ได้ เช่น "features.item" เพื่อให้ parser ทยอยส่ง feature แต่ละตัวมาให้เราประมวลผลแล้วทิ้งข้อมูลเก่าไปทันที ช่วยลดภาระหน่วยความจำได้อย่างมหาศาล
Step 5: Performing the Same Analysis With ijson
ลองปรับปรุงโค้ดใน stream_json.py โดยใช้โมเดลการประมวลผลแบบ stream:
import ijson
import time
start = time.perf_counter()
total = 0
market_count = 0
first_five = []
with open("citylots.json", "rb") as f:
for feature in ijson.items(f, "features.item"):
total += 1
properties = feature["properties"]
if properties.get("STREET") == "MARKET":
market_count += 1
if len(first_five) < 5:
first_five.append(properties.get("BLKLOT"))
elapsed = time.perf_counter() - startเมื่อวัดผลด้วย memory-profiler อีกครั้ง พบว่าจุดสูงสุดของการใช้หน่วยความจำ (peak memory usage) ลดลงเหลือเพียง 46.1 MiB เท่านั้น เทียบกับวิธีเดิมที่ใช้ถึง 808.9 MiB
| แนวทาง | ไฟล์ Input | จุดสูงสุดของหน่วยความจำ | โมเดลการประมวลผล |
|---|---|---|---|
json.load() | 181 MB | 808.9 MiB | โหลดทุกอย่าง |
ijson.items() | 181 MB | 46.1 MiB | Stream records |
สรุปได้ว่าการประมวลผลไฟล์ JSON ขนาดใหญ่อย่างมีประสิทธิภาพไม่ได้ขึ้นอยู่กับรูปแบบไฟล์ แต่ขึ้นอยู่กับวิธีการจัดการข้อมูล หากงานของคุณต้องการข้อมูลเพียงทีละส่วน การเลือกใช้เครื่องมือที่สนับสนุนการทำ streaming อย่าง ijson จะช่วยให้ระบบทำงานได้เสถียรและประหยัดทรัพยากรมากขึ้น
ความคิดเห็น (0)
เข้าสู่ระบบเพื่อร่วมแสดงความเห็น
สมัครสมาชิกมาเป็นคนแรกที่แสดงความเห็นกันเลยโบร
