Xử lý file CSV lớn với Ruby
Bài đăng này đã không được cập nhật trong 6 năm
Xử lý những file dung lượng lớn có thể tiêu tốn rất nhiều ram. Chúng ta cũng thử một số cách xử lý dưới đây và đo tốc độ và lượng ram đã sử dụng:
Chuẩn bị dữ liệu
Trước khi bắt đầu, chúng ta chuẩn bị 1 file CSV data.csv
với 1 triệu dòng (khoảng 75MB)
require 'csv'
require_relative './helpers'
headers = ['id', 'name', 'email', 'city', 'street', 'country']
name = "Pink Panther"
email = "pink.panther@example.com"
city = "Pink City"
street = "Pink Road"
country = "Pink Country"
print_memory_usage do
print_time_spent do
CSV.open('data.csv', 'w', write_headers: true, headers: headers) do |csv|
1_000_000.times do |i|
csv << [i, name, email, city, street, country]
end
end
end
end
Bộ nhớ được sử dụng và thời gian tiêu tốn
Dưới đây là script để tính toán bộ nhớ đã sử dụng và thời gian tiêu tốn để chạy các lệnh mà chúng ta cần test
require 'benchmark'
def print_memory_usage
memory_before = `ps -o rss= -p #{Process.pid}`.to_i
yield
memory_after = `ps -o rss= -p #{Process.pid}`.to_i
puts "Memory: #{((memory_after - memory_before) / 1024.0).round(2)} MB"
end
def print_time_spent
time = Benchmark.realtime do
yield
end
puts "Time: #{time.round(2)}"
end
Đây là kết quả khi tạo file data.csv
:
$ ruby generate_csv.rb
Time: 5.17
Memory: 1.08 MB
Quá trình này tốn 5.17s và 1.08MB ram. Kích thước file là 75MB
$ ls -lah data.csv
-rw-rw-r-- 1 dalibor dalibor 75M Mar 29 00:34 data.csv
Đọc CSV từ file trong cùng 1 lúc (CSV.read)
require_relative './helpers'
require 'csv'
print_memory_usage do
print_time_spent do
csv = CSV.read('data.csv', headers: true)
sum = 0
csv.each do |row|
sum += row['id'].to_i
end
puts "Sum: #{sum}"
end
end
Kết quả chạy:
$ ruby parse1.rb
Sum: 499999500000
Time: 19.84
Memory: 920.14 MB
Phương pháp này tiêu tốn 920MB ram, bời vì chúng ta xây dựng toàn bộ đối tượng CSV trong bộ nhớ. Việc đó gây ra nhiều đối tượng String được tạo bời thư việc CSV và bộ nhớ được sử dụng nhiều hơn so với kích thước thực của fiel CSV.
Phân tích CSV từ bộ nhớ String (CSV.parse)
require_relative './helpers'
require 'csv'
print_memory_usage do
print_time_spent do
content = File.read('data.csv')
csv = CSV.parse(content, headers: true)
sum = 0
csv.each do |row|
sum += row['id'].to_i
end
puts "Sum: #{sum}"
end
end
Kết quả:
$ ruby parse2.rb
Sum: 499999500000
Time: 21.71
Memory: 1003.69 MB
Lượng ram tiêu tốn rất nhiều 1003.69 MB.
Phân tích CSV theo từng dòng từ String trong bộ nhớ (CSV.new)
require_relative './helpers'
require 'csv'
print_memory_usage do
print_time_spent do
content = File.read('data.csv')
csv = CSV.new(content, headers: true)
sum = 0
while row = csv.shift
sum += row['id'].to_i
end
puts "Sum: #{sum}"
end
end
Kết quả:
$ ruby parse3.rb
Sum: 499999500000
Time: 9.73
Memory: 74.64 MB
Từ kết quả ta thấy lượng Ram sử dụng và kích thước tệp khá bằng nhau vì nội dung của file được tài trong bộ nhwos và xử lý nhanh gấp đôi. Cách này rất hữu ích khi chúng ta có nội dung và không cần đọc chúng từ file và chúng ta muốn lặp nó theo từng dòng.
Phân tích CSV file từng dòng từ IO object
require_relative './helpers'
require 'csv'
print_memory_usage do
print_time_spent do
File.open('data.csv', 'r') do |file|
csv = CSV.new(file, headers: true)
sum = 0
while row = csv.shift
sum += row['id'].to_i
end
puts "Sum: #{sum}"
end
end
end
Kết quả:
$ ruby parse4.rb
Sum: 499999500000
Time: 9.88
Memory: 0.58 MB
Cách này tiêu tốn rất ít ram, chỉ 0.58MB. Tuy nhiên thời gian lại chậm hơn, 9.88s.
Trong thư viện CSV có 1 có chế cho việc này. CSV.foreach
:
require_relative './helpers'
require 'csv'
print_memory_usage do
print_time_spent do
sum = 0
CSV.foreach('data.csv', headers: true) do |row|
sum += row['id'].to_i
end
puts "Sum: #{sum}"
end
end
Kết quả:
$ ruby parse5.rb
Sum: 499999500000
Time: 9.84
Memory: 0.53 MB
Cách cuối cùng naỳ sẽ phù hợp khi cần xử lý tệp cực lớn từ 10BG trờ lên. Vì chúng ta ko thể tiêu tốn 10GB ram chỉ để thực hiện việc này. Còn thời gian thì có thể tốn nhiều hơn 1 chút
Nguồn
https://dalibornasevic.com/posts/68-processing-large-csv-files-with-ruby
All rights reserved