Écrire des méthodes Ruby en utilisant C (Numo :: NArray)

(Ajouté le 29 août 2020) Lors du passage d'une instance de Numo :: DFloat comme argument Numo::DFloat(view)#shape= Il peut ne pas être possible de passer correctement quand c'est en vue comme. Cela se produit lorsque vous découpez une partie du tableau. Dans ce cas, si vous le copiez avec dup puis que vous le passez, il n'y aura pas de problème. Cela ne semble pas fonctionner uniquement en cas de casting.

introduction

Reportez-vous à "Ecriture de méthodes Ruby en C ++" pour savoir comment écrire des bibliothèques d'extensions Ruby en C ++. Je pense que vous pouvez créer une bibliothèque avec presque aucun effort tant que vous écrivez C ++. Ici, nous allons vous montrer comment transformer une fonction écrite en C en une bibliothèque d'extensions.

Passer à une variable simple

À titre d'exemple simple, j'ai écrit une fonction qui passe une variable simple. C'est juste une fonction C normale. Créons une bibliothèque d'extensions qui peut être appelée depuis Ruby en utilisant SWIG. Une fonction qui renvoie deux fois la valeur et une fonction qui renvoie la longueur de la chaîne.

test1.h


#include <string.h>

double twicefold_d(double x);
int string_length(char* str);

test1.c


#include "test1.h"

double twicefold_d(double x){
  return(x*2.0);
}

int string_length(char* str){
  return(strlen(str));
}

Il est pratique de créer un fichier d'en-tête séparé, c'est pourquoi nous le faisons. La deuxième fonction prend une chaîne comme argument. Ensuite, créez un fichier pour SWIG. Le testF du module est le nom du module lors de l'appel depuis Ruby (cependant, le premier caractère est converti en majuscule). Il s'agit simplement de lire le fichier d'en-tête.

test.i


%module testF
%{
#include "test1.h"
%}

%include test1.h

extconf.rb


require 'mkmf'
create_makefile("testF")

Placez les fichiers ci-dessus dans le même dossier et exécutez la commande suivante.

swig -ruby test.i
ruby extconf.rb
make

Vous aurez testF.bundle (l'extension dépend du système d'exploitation). Programme de test

test1.rb


require "./testF"
p TestF.twicefold_d(3.4)
p TestF.string_length("abcd")

Dans l'exemple ci-dessus, une seule valeur de retour peut être prise, mais plusieurs valeurs de retour sont possibles à l'aide de l'argument pointeur. Veuillez vous y référer tel qu'il est écrit dans le manuel SWIG. * Recherchez avec OUTPUT pour savoir comment le faire.

Lors du passage d'un tableau

Il existe plusieurs façons de transmettre un tableau. Le manuel décrit comment utiliser carray.i, mais il est assez difficile à utiliser.

Comment utiliser Numo :: NArray

Numo :: NArray peut effectuer un traitement à grande vitesse, donc je pense que beaucoup de gens l'utilisent. Je vais vous montrer comment le passer à la fonction écrite en C via celui-ci.

Une fonction qui reçoit un tableau et calcule la somme et une fonction qui calcule la valeur double. int n est le nombre d'éléments dans le tableau et double x [] est le pointeur vers le tableau.

test2.h


double sum_d(int n, double x[]);
void twicefold_dv(int n, double x[], double y[]);

test2.c


# include "test2.h"

double sum_d(int n, double x[]){
  double sum=0;
  for(int i=0; i<n; i++){
    sum +=x[i];
  }
  return(sum);
}


void twicefold_dv(int n, double x[], double y[]){
  for(int i=0; i<n; i++){
    y[i]=x[i]*2.0;
  }
}

test.i


%module testF
%{
#include "numo/narray.h"
#include "test2.h"
%}

%typemap(in) double [] {
  narray_t *nary;
  GetNArray($input, nary);
  $1 = ($1_ltype)na_get_pointer_for_read($input);
}
%include test2.h

%typemap(in) double [] { Donc, il est appliqué à l'argument qui correspond à double [] dans le fichier d'en-tête (test2.h), double ?? []. Ici, je passe juste un pointeur vers les données de NArray à la fonction, ne vérifiant pas du tout le type de données et ne transmettant la taille du tableau qu'avec int n, donc si je fais une erreur, j'obtiendrai certainement une erreur. Devenir. Renvoi des données avec double y []. C'est une méthode assez approximative, mais c'est un niveau où vous pouvez quand même passer des données de tableau.

extconf.rb


equire 'mkmf'
dir_config("numo-narray")
create_makefile("testF")

Une deuxième ligne a été ajoutée pour obtenir la position de numo / narray.h.

swig -ruby test.i
ruby extconf.rb  -- --with-numo-narray-include=/opt/local/lib/ruby2.7/gems/2.7.0/gems/numo-narray-0.9.1.8/lib/numo/
make

La deuxième ligne vous indique l'emplacement de numa / narray.h, veuillez donc le réécrire en fonction de votre environnement. Ne faites pas d'erreur car il se trouve dans le dossier numa qui contient ceci, pas narray.h.

test2.rb


require "numo/narray"
require "./testF"

p TestF.sum_d(4, Numo::DFloat.cast([1.0,2.1,3.2,4.3]))
result= Numo::DFloat.zeros(4)
TestF.twicefold_dv(4, Numo::DFloat.cast([1.0,2.1,3.2,4.3]), result)
p result

édition révisée

Les données NArray elles-mêmes contiennent des informations sur la taille du tableau, je l'ai donc amélioré pour l'utiliser. Il convertit également le type de données en Numo :: DFloat. (Le type de données à convertir est spécifié par numa_cDFloat) Ici, au lieu de lire avec% include comme précédemment, il est écrit directement en bas de test.i.

Il y a deux% typemap (in), mais le premier double sum_d(int SIZE, double *NARRAY); Correspond à. Le second est void twicefold_dv(int SIZE, double *NARRAY_in, double *NARRAY_out); J'essaye de correspondre. Ceci est encore amélioré pour créer un NArray pour la valeur de retour et le renvoyer.

test.i


%module testF
%{
#include "numo/narray.h"
#include "test2.h"
%}

%typemap(in) (int SIZE, double *NARRAY) {
  narray_t *nary;
  VALUE obj1;
  obj1 = rb_funcall(numo_cDFloat, rb_intern("cast"), 1, $input);
  GetNArray(obj1, nary);
  $2 = ($2_ltype)na_get_pointer_for_read(obj1);
  $1 = NA_SHAPE(nary)[0];
}

%typemap(in) (int SIZE, double *NARRAY_in, double *NARRAY_out) (VALUE temp) {
  narray_t *nary1, *nary2;
  VALUE obj1, obj2;
  size_t obj2_shape[1];
  obj1 = rb_funcall(numo_cDFloat, rb_intern("cast"), 1, $input);
  GetNArray(obj1, nary1);
  $2 = ($2_ltype)na_get_pointer_for_read(obj1);
  obj2_shape[0] = NA_SHAPE(nary1)[0];
  obj2 = rb_funcall(nary_new(numo_cDFloat, 1, obj2_shape), rb_intern("fill"), 1, INT2FIX(0));
  temp=obj2;
  GetNArray(obj2, nary2);
  $3 = ($3_ltype)na_get_pointer_for_read(obj2);
  $1 = NA_SHAPE(nary1)[0];
}

%typemap(argout) (int SIZE, double *NARRAY_in, double *NARRAY_out){
  $result=temp1;
}

double sum_d(int SIZE, double *NARRAY);
void twicefold_dv(int SIZE, double *NARRAY_in, double *NARRAY_out);

Il est conçu pour renvoyer le tableau calculé par% typemap (argout), mais il n'est pas lié à temp dans% typemap (in). Puisque le nom de variable de temp ici a été converti en temp1, nous nous attendons à ce qu'il soit temp1 dans% typemap (argout). C'est une façon d'écrire assez dangereuse, mais je n'ai pas trouvé de bonne façon, donc c'était un compromis. Si cela provoque une erreur, réécrivez-la. Vous pouvez le voir en regardant le contenu de test_wrap.c.

Je n'ai pas vérifié les dimensions du tableau, mais il y a des informations dans narray.h, veuillez donc les ajouter si nécessaire.

Il n'y a pas de changement dans les autres fichiers, vous pouvez donc l'utiliser tel quel. Les commandes sont exactement les mêmes.

test3.rb


require "numo/narray"
require "./testF"

p TestF.sum_d(Numo::DFloat.cast([1.0,2.1,3.2,4.3]))
p TestF.twicefold_dv(Numo::DFloat.cast([1.0,2.1,3.2,4.3]))

p TestF.sum_d(Numo::SFloat.cast([1.0,2.1,3.2,4.3]))

macOS 10.13.6 Ruby 2.7.1 SWIG 4.0.2

Recommended Posts

Écrire des méthodes Ruby en utilisant C (Numo :: NArray)
Écrire des méthodes Ruby en utilisant C (Partie 1)
Écrire des méthodes Ruby à l'aide de C ++ (Partie 2) Benchmark
Écrire du code à l'aide de classes et d'instances Ruby
À propos des méthodes Ruby
Essayez d'utiliser la méthode java.lang.Math
À propos des méthodes d'instance Ruby
Méthode [Ruby], méthode d'instance ...
J'ai écrit un analyseur C (comme) en utilisant PEG dans Ruby