First things first
Hey, bạn Thâm đây
Như tiêu đề bài viết thì trong bài này mình sẽ thử nghiệm các cách để đọc file CSV, rồi chọn ra cách tối ưu nhất ở phương diện tiêu thụ RAM, cũng như tốc độ xử lý. Let’s go
Chuẩn bị file test
require 'csv'
require_relative './helpers'
headers = ['id', 'name', 'email', 'city', 'street', 'country']
name = "Tham Davies"
user_name = "boygialaideptraikhongyeuai"
email = "[email protected]"
city = "Pleiku"
street = "Le Duan"
country = "Viet Nam"
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, user_name, email, city, street, country]
end
end
end
end
Helpers
Tiếp theo, chúng ta sẽ sử dụng Benchmark để đo thời gian và đo lường việc sử dụng bộ nhớ.
print_memory_usage
sẽ tính toán và in ra lượng bộ nhớ sử dụng trước và sau khi thực hiện các thao tác.print_time_spent
sẽ tính toán và in ra thời gian thực hiện các thao tác.
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)} seconds"
end
Sau khi có 2 file trên thì các bạn chạy câu lệnh bên dưới để tiến hành tạo file test
$ ruby generate_csv.rb
Time: 3.61
Memory: 1.67 MB
$ ls -lah data.csv
-rw-r--r--@ 1 tham tham 85M Nov 22 10:45 data.csv
Kết quả ở trên tuỳ thuộc vào máy, và điểm mấu chốt ở đây là Ruby sử dụng rất ít bộ nhớ (~1.7MB) để generate file CSV có kích thước là 85MB vì cơ chế dọn rác (GC) đã thu hồi lại bộ nhớ đã sử dụng.
Các cách đọc file CSV
1. 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ả:
$ ruby parse1.rb
Sum: 499999500000
Time: 20.12
Memory: 894.5 MB
Qoáo, cách này tiêu tốn 900MB RAM, mất 20S để đọc file, hãy thử tưởng tượng file của bạn tầm 10GB thì sẽ như thế nào 😄
Một số bạn sẽ thắc mắc tại sao file chỉ 85MB mà khi đọc file lại chiếm x10 bộ nhớ RAM?
Lý do là vì chúng ta đã đọc toàn bộ file và lưu đối tượng CSV trong bộ nhớ, khi làm như vậy thư viện CSV tạo ra rất nhiều đối tượng String, nên bộ nhớ sử dụng cao hơn nhiều so với kích thước thực tế của file CSV.
2. 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: 14.61
Memory: 1066.35 MB
Cách này thậm chí còn tiêu tốn RAM hơn Cách 1, lý do là mình load toàn bộ dữ liệu từ file lưu vào biến (85MB), sau đó mới bắt đầu thực hiện các thao tác như Cách 1.
3. CSV.new
Bây giờ chúng ta thử cách load toàn bộ file
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: 6.7
Memory: 170.19 MB
Từ kết quả ta thấy lượng bộ nhớ sử dụng x2 kích thước của file (85 MB) vì nội dung file đã được nạp vào bộ nhớ. Thời gian xử lý nhanh hơn gấp đôi.
Phương pháp này hữu ích khi chúng ta đã có nội dung mà không cần đọc từ file, chỉ cần duyệt từng dòng. Việc lưu trực tiếp nội dung file vào bộ nhớ cho phép tăng tốc độ xử lý bằng cách loại bỏ quá trình đọc/ghi liên tục từ ổ đĩa.
Cách này được xem là cách tối ưu rồi, nhưng còn cách nào tốt hơn không? Cùng xem ví dụ ở dưới nhé.
4. IO read
Ở đây chúng ta mở file, sau đó đọc từng dòng và parse sang CSV object
require_relative './helpers'
require 'csv'
# cách 1: bằng cơm
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
# cách 2: dùng built-in của Ruby
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 parse4.rb
Sum: 499999500000
Time: 9.57
Memory: 0.72 MB
Á chà chà, cách này tiêu tốn rất ít RAM, nhưng đổi lại là tốc độ xử lý chậm hơn một tẹo Cách 3 vì nhiều thao tác IO hơn.
Lời kết
Trong bài viết này, chúng ta đã thử nghiệm các cách để đọc file CSV và tìm ra cách tối ưu nhất từ góc nhìn tiêu thụ RAM và tốc độ xử lý. Chúng ta đã thấy rằng cách sử dụng CSV.foreach
là cách tiết kiệm RAM nhất, dù tốc độ xử lý có chậm hơn so với cách sử dụng CSV.new
. Tuy nhiên, tùy thuộc vào yêu cầu và tình huống cụ thể, chúng ta có thể lựa chọn phương pháp phù hợp để đạt được hiệu suất tối ưu.
Nếu chúng ta xử lý file CSV dung lượng lớn thì rõ ràng cách tiêu thụ ít RAM nhất là cách hiệu quả nhất bởi nó sẽ không gây tràn RAM gây crash hệ thống.
2 comments
Hay qué anh zai Tây…..Nguyên ơi
cảm ơn sếp Zách không xách dép =))