12/08/2018, 16:43

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

0