Ruby 1.9.3 の CSV
で行数の多い CSV を作っていたところ,長い時間がかかった.
ruby-prof で測定したところ,#<<
から呼ばれる Encoding::compatible?
が大部分を占めていた.
$ cat large-csv.rb
require 'csv'
require 'stringio'
file = StringIO.new
csv = CSV.new(file)
100_000.times do
csv << Array.new(10,'a'*20)
end
p file.length
$ ruby-prof -f before.html -p call_stack large-csv.rb
21000000

この部分のコードを以下に引用する.
https://github.com/ruby/ruby/blob/v1_9_3_392/lib/csv.rb#L1730-1732
if @io.is_a?(StringIO) and
output.encoding != raw_encoding and
(compatible_encoding = Encoding.compatible?(@io.string, output))
最初の 2 条件が満たされると,Encoding.compatible?
が呼ばれてしまう.
第 1 条件:以下引用するコードによると,::new
の第一引数に String
あるいは StringIO
を与えると @io
は StringIO
となる.
https://github.com/ruby/ruby/blob/v1_9_3_392/lib/csv.rb#L1563-1568
def initialize(data, options = Hash.new)
# build the options for this read/write
options = DEFAULT_OPTIONS.merge(options)
# create the IO object we will read from
@io = data.is_a?(String) ? StringIO.new(data) : data
第 2 条件左辺:以下引用するコードによると,::new
の第二引数の :encoding
オプションに与えた値と #<<
に与えた Array
中の String
の #encoding
によって output.encoding
は決定される.
https://github.com/ruby/ruby/blob/v1_9_3_392/lib/csv.rb#L1729
output = row.map(&@quote).join(@col_sep) + @row_sep # quote and separate
https://github.com/ruby/ruby/blob/v1_9_3_392/lib/csv.rb#L2090-2116
@force_quotes = options.delete(:force_quotes)
do_quote = lambda do |field|
field = String(field)
encoded_quote = @quote_char.encode(field.encoding)
encoded_quote +
field.gsub(encoded_quote, encoded_quote * 2) +
encoded_quote
end
quotable_chars = encode_str("\r\n", @col_sep, @quote_char)
@quote = if @force_quotes
do_quote
else
lambda do |field|
if field.nil? # represent +nil+ fields as empty unquoted fields
""
else
field = String(field) # Stringify fields
# represent empty fields as empty quoted fields
if field.empty? or
field.count(quotable_chars).nonzero?
do_quote.call(field)
else
field # unquoted field
end
end
end
end
https://github.com/ruby/ruby/blob/v1_9_3_392/lib/csv.rb#L2023-2025
@col_sep = options.delete(:col_sep).to_s.encode(@encoding)
@row_sep = options.delete(:row_sep) # encode after resolving :auto
@quote_char = options.delete(:quote_char).to_s.encode(@encoding)
第 2 条件右辺:いっぽう,以下引用するコードによると,raw_encoding
は,@io
のそれとなる.
https://github.com/ruby/ruby/blob/v1_9_3_392/lib/csv.rb#L2326-2328
def raw_encoding(default = Encoding::ASCII_8BIT)
if @io.respond_to? :internal_encoding
@io.internal_encoding || @io.external_encoding
StringIO#set_encoding
でこれを指定することができる.
以上に従って,次のようにして CSV を作成した.
$ cat large-csv-modified.rb
require 'csv'
require 'stringio'
file = StringIO.new
file.set_encoding(Encoding::UTF_8)
csv = CSV.new(file, {:encoding => Encoding::UTF_8})
100_000.times do
csv << Array.new(10,('a'*20).encode(Encoding::UTF_8))
end
p file.length
$ ruby-prof -f after.html -p call_stack large-csv-modified.rb
21000000

時間のかかっていた Encoding::compatible?
が呼ばれなくなった.