Xử lý file CSV lớn với Ruby
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 ...
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