Help us understand the problem. What is going on with this article?

C++を使ってRubyのメソッドを書く(その2) ベンチマーク

目次

C++を使ってRubyのメソッドを書く(その1)
C++を使ってRubyのメソッドを書く(その2) ベンチマーク <- ここ

ベンチマーク

配列の合計を計算する単純なプログラムです。上記のその1で書いた方法(1)と
Cを使ってRubyのメソッドを書く(その2)Numo::NArray (2)の速度を比較してみました。
他にも、 Numo::SFloat型のデータを渡した場合(3) (Numo::DFloatにキャスト後計算)、
NArrayをArrayに変換してから(1)と同じ方法(4)、
ArrayをNumo::DFloatに変換してから(2)と同じ方法(5)、
Rubyの組込み関数Array#sumを使う方法(6)、
Rubyの組込み関数Array#injectを使う方法(7)、
Numo::DFloatの関数sumを使う方法(8)
の速度を比較しています。

結果は最後に書いています。

その1の方法(1)でも十分な速度がありますが、(2)が最高速でした。(6)のArray#sumがかなり速く、バイナリで書かれたRubyの関数は速度で期待できそうです。Numo::DFloat#sumが意外と遅いのとArrayからNumo::DFloatへの変換も遅めのような気がします。

使用したプログラム

test.cpp
#include "test.hpp"

double sum(const std::vector<double>& ary){
    double sum=0.0;
    for (int i=0; i<ary.size(); i++){
        sum+=ary[i];
    }
    return(sum);
}

double sum_nd(int n, double *ary){
    double sum=0.0;
    for (int i=0; i<n; i++){
        sum+=ary[i];
    }
    return(sum);
}
test.hpp
#include <vector>
double sum(const std::vector<double>& ary);
double sum_nd(int n, double *ary);
test.i
%module testF
%{
#include "numo/narray.h"
#include "test.hpp"
%}

%include <std_vector.i>
%template(DoubleVector) std::vector<double>;
extern double sum(std::vector<double> ary);

%typemap(in) (int LENGTH, double *NARRAY_in){
  narray_t *nary;
  if (rb_obj_class($input)!=numo_cDFloat){
      $input = rb_funcall(numo_cDFloat, rb_intern("cast"), 1, $input);
  }
  GetNArray($input, nary);
  if (NA_TYPE(nary)==NARRAY_VIEW_T){
    $input = rb_funcall($input, rb_intern("dup"), 0);
    GetNArray($input, nary);
  }
  $2 = ($2_ltype)na_get_pointer_for_read($input);
  $1 = NA_SIZE(nary);
}
extern double sum_nd(int LENGTH, double *NARRAY_in);
extconf.rb
require 'mkmf'
dir_config("numo/narray")
have_header("numo/narray.h")
create_makefile("testF")
swig -c++ -ruby test.i
ruby extconf.rb  -- --with-numo/narray-include=/opt/lib/ruby/gems/2.6.0/gems/numo-narray-0.9.1.8/lib/numo/
make

ベンチマーク用プログラム

benchmark.rb
require "benchmark"
require "numo/narray"
require "./testF"

data=[*1..100].map(&:to_f)
data_na=Numo::DFloat.cast(data)
data_naf=Numo::SFloat.cast(data)

puts Benchmark::CAPTION
puts Benchmark.measure{
    1000000.times{
        TestF::sum(data) # (1)
    }
}
=begin
以下
TestF::sum_nd(data_na)   # (2)
TestF::sum_nd(data_naf)  # (3)
TestF::sum(data_na.to_a) # (4)
TestF::sum_nd(Numo::DFloat.cast(data)) # (5)
data.sum                 # (6)
data.inject(:+)          # (7)
data_na.sum              # (8)
=end

結果

データの流れ 実行時間 (μs)
(1) Array => vector 2.44
(2) Numo::DFloat => double [] 0.14
(3) Numo::SFloat => cast -> double [] 1.59
(4) Numo::DFloat -> Array => vector 4.77
(5) Array -> Numo::DFloat => double [] 10.11
(6) Array#sum 0.60
(7) Array#inject(:+) 3.42
(8) Numo::DFloat#sum 1.30

(実行時間はループ一回分に換算しています)

ki073
業務の手抜き?をできるようにRubyやRを中心に使っています。今は高速化をねらってHaskellにも手を出しています。最適化問題で困っている人が多いので最近ではGLPKのプログラムを多く公開しています。質問や要望などがありましたらコメント欄に書き込んでください。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away