Ruby

File.readを多用しすぎるとパフォーマンスが低下する

fjordbootcampのプラクティスで、「コマンドラインで指定したファイルを読み込み、ファイル情報を出力する」コマンドを作成しました。

このコマンドを作成しているときにFile.readを多用していたのですが、メンターから「繰り返し処理のときにFile.readばかり使うとパフォーマンスが落ちるよ」と指摘されました!

「そういう考え方をするのかー!」と驚いたのでブログにまとめておこうと思います。
結論は以下の通りです。

結論
  • eachメソッド等の繰り返し処理をするときにFile.readを使うと指定された回数だけファイルを読み込まないといけない
  • File.read(パス名)を変数に代入して、ファイルの読み込みを一度だけで済むためパフォーマンスの低下を予防できる

File.read

Fileクラス

Fileクラスはファイルにアクセスするためのクラスです。
例えば、File.openはファイルを開くことができます。

s = File.open(ファイル名)
# 指定したファイルを開くことができる

IOクラス

File.readのreadはIOクラスのメソッドです。
上記のようにFile.openでファイルを開いてから、readメソッドを使うことでファイルの情報を読み込むことができます。

s = File.open(ファイル名)
puts s.read
# ファイル情報が出力される

File.readの多用はパフォーマンスを低下させる

例えば以下のようなコードがあるとします。

def output_wc_for_file_path(filepaths)
  filepaths.each do |filepath|
    print count_lines_for_text(filepath)
    print count_words_for_text(filepath)
    print count_bytes_for_text(filepath)
  end
end

# 標準入力に対応させる
def count_lines(standard_input)
  standard_input.count("\n")
end

def count_words(standard_input)
  standard_input.split(/\s+/).count
end

def count_bytes(standard_input)
  standard_input.size
end

# ファイルパス
def count_lines_for_text(filepath)
  count_lines(File.read(filepath))
end

def count_words_for_text(filepath)
  count_words(File.read(filepath))
end

def count_bytes_for_text(filepath)
  count_bytes(File.read(filepath))
end

filepaths = ARGV
standard_input = $stdin.read

output_wc_for_file_pathで、’count_lines_for_text’、’count_words_for_text’、’count_bytes_for_text’を出力するたびにFile.readによってファイルが繰り返し読み込まれます。

読み込まれるファイルのサイズが500MBあるとしたら毎回500MBを処理しないといけません。読み込む回数とファイルサイズが多ければ、その分PCへの負担が増えるためパフォーマンスが低下しています。

File.readの改善策

def output_wc_for_file_path(filepaths)
  filepaths.each do |filepath|
   text = File.read(filepath) 
    print count_lines_for_text(text)
    print count_words_for_text(text)
    print count_bytes_for_text(text)
  end
end

# 標準入力に対応させる
def count_lines(standard_input)
  standard_input.count("\n")
end

def count_words(standard_input)
  standard_input.split(/\s+/).count
end

def count_bytes(standard_input)
  standard_input.size
end

# ファイルパス
def count_lines_for_text(filepath)
  count_lines(File.read(filepath))
end

def count_words_for_text(filepath)
  count_words(File.read(filepath))
end

def count_bytes_for_text(filepath)
  count_bytes(File.read(filepath))
end

filepaths = ARGV
standard_input = $stdin.read

textにFile.read(filepath)を代入することで、ファイルを読み込む回数を1回にすることができます。
ファイルの読み込む回数が減ることでパフォーマンスの低下を防ぐことができます。