Array#lengthと引数・ブロックなしArray#countの違い
自分はArrayで使える#lengthと#countの違いを生まれてこの方意識した事がなかったのですが、配列の長さを数えるだけであれば、lengthの方がcountより早いという話を聞いたので、自分なりに根拠を調べて見ました。
調査
そもそも両者のドキュメントを比べてみると、lengthは純粋に長さを求めるだけなのに対して、countは引数やブロックに数える対象の条件を加えることが出来ます。
※ MRIの場合countはArray用にメソッドがあるのですが、るりま的にはEnumerableがそれを代理しているようだったので、一旦はそちらのリンクを貼っています。
この時点ですでに何か違うのかなという気はするのですが、今回は「lengthの方がcountより早い」の根拠が知りたいのでもう少し深掘りしてみます。
まずlengthの方を見ていきます。
https://ruby-doc.org/core-2.6.3/Array.html#method-i-length
こちらの方はRARRAY_LEN
というマクロに配列を渡して、その長さを返しています。
static VALUE rb_ary_length(VALUE ary) { long len = RARRAY_LEN(ary); return LONG2NUM(len); }
ここで気になるのは RARRAY_LEN
マクロの実装なので、それを追ってみます。
#define RARRAY_LEN(a) rb_array_len(a)
static inline long rb_array_len(VALUE a) { return (RBASIC(a)->flags & RARRAY_EMBED_FLAG) ? RARRAY_EMBED_LEN(a) : RARRAY(a)->as.heap.len; }
RARRAY_EMBED_LENは短い配列のときだけ使用する物らしいので一旦読み飛ばすと、RARRAY(a)->as.heap.len
をRARRAY_LEN
は返しているようです。
RARRAY(a)->as.heap.len
は構造体メンバの静的な値を返しているだけな為、O(1)で配列の長さを返せるようになっています。
次にcountを見ていきます。
https://ruby-doc.org/core-2.6.3/Array.html#method-i-count
委細は省くのですが、countが引数なし、ブロックなしで呼ばれる場合の動作はreturn LONG2NUM(RARRAY_LEN(ary)
になる為、lengthと変わらないように見えます。
違うところと言えば、rb_check_arity
と rb_block_given_p
を呼び出しているところ位なので、これのオーバーヘッド分lengthが有利になるかなという気はしました。
static VALUE rb_ary_count(int argc, VALUE *argv, VALUE ary) { long i, n = 0; if (rb_check_arity(argc, 0, 1) == 0) { VALUE v; if (!rb_block_given_p()) return LONG2NUM(RARRAY_LEN(ary)); for (i = 0; i < RARRAY_LEN(ary); i++) { v = RARRAY_AREF(ary, i); if (RTEST(rb_yield(v))) n++; } } else { VALUE obj = argv[0]; if (rb_block_given_p()) { rb_warn("given block not used"); } for (i = 0; i < RARRAY_LEN(ary); i++) { if (rb_equal(RARRAY_AREF(ary, i), obj)) n++; } } return LONG2NUM(n); }
手元のPCで検証してみると、わずかではありますがlengthの方が早かったです。
検証ソース
start = Time.now (0...10_000_000).each do a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] a.length end finish = Time.now puts "length:#{finish - start}" start = Time.now (0...10_000_000).each do a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] a.count end finish = Time.now puts "count:#{finish - start}"
結果
ruby test.rb length:1.01813 count:1.258869