Механика XS: Модули

Механика XS

Эта статья про XS. Она объясняет что это, зачем это, как это работает, и как это использовать. Она включает полный, работающий пример XS модуля, а также базовую основу модуля, который вы можете использовать как отправную точку для своего кода. Вкратце, основной целью данной статьи является предоставление необходимых основ и информации для того, чтобы вы создавали свои XS модули.

Данная статья состоит из пяти частей:

Ноябрь Введение мотивация, определения, примеры
Декабрь Архитектура интерпретатор Perl, соглашения вызывов, представление данных
Январь Инструменты h2xs, xsubpp, DynaLoader
Февраль Модули Math::Ackermann, Set::Bit
Март Align::NW глобальное оптимальное выравнивание последовательностей Нидлмана-Вунша

Модули

В первой части этой статьи, мы определили задачу, решение которой в виде реализации на XS могла бы получить преимущества. Два месяца назад, мы рассказали про архитектуру XS. В последнем месяце, мы написали XS-код. В этом месяце, мы пишем модули XS.

Во-первых, мы обсудим некоторые варианты решений проектирования модулей XS. Затем мы покажем примеры, которые иллюстрируют различные подходы.

Проектирование

Говоря в широком смысле, модуль XS это любой модуль, который содержит XS-код.

Perl не имеет ни классов, ни объектов per se. Скорее всего, он имеет набор функций, которые вместе [неявно] поддерживают модель объектно-ориентированного программирования (ООП). Они включают в себя

  • подпрограмма (subroutine): метод в классе
  • пакет: пространство имен класса
  • модуль: файл, содержащий код класса
  • ссылка (reference): handle объекта
  • use: загружает и инициализирует класс
  • bless: ассоциирует объект с классом
  • ->: вызывает методы объекта

XS это просто еще одна функция в наборе, и существует различные пути его использования. Более конкретно, XS модули могут представлять данные либо в [представлении] C, либо Perl, и могут иметь методы, написанные либо на C, либо на Perl.

Если мы представим данные объектов в C, то методы, написанные на C – т.е., xsub’ы – имеют нативный доступ к данным. Методы, написанные на Perl должны получать доступ к данным объектов через методы-акцессоры.

Если мы представим данные объектов на Perl, тогда методы, написанные на Perl имеют нативный доступ к данным. Методы, написанные на C должны получать доступ к данным объектов через Perl C API.

Эта таблица суммируют четыре возможных варианта представления и получения доступа к данным.

                        Данные
              -------------+-----------------
              |    Perl    |        C
-------+------+------------+-----------------
 Метод | Perl |    натив   | метод акцессора
 -//-  | xsub | Perl C API |      натив

Представление данных в Perl может быть проще; представление данных в C может быть обязательным для достижения производительности. Мы дадим пример каждого подхода к завершению данной статьи.

Math::Ackermann

Функция Аккерманна определена рекурсивно как

A(0  , n  ) = n + 1
A(m+1, 0  ) = A(m, 1)
A(m+1, n+1) = A(m, A(m+1, n))

Она, возможно, не имеет аналогов по потреблению столь огромных объемов ОЗУ и ресурсов ЦП при очень маленьком объеме кода, в то же время выполняя роль как минимум символической утилиты.

Поскольку это столь интенсивная функция на ЦП, мы захотели бы написать модуль, который делает две вещи

  • функцию расчета на C
  • запоминание результатов (кэширование)

Кэширование значений проще выполнить на Perl.

package Math::Ackermann;

sub new
{
    my $class = shift;
    bless [], $class
}

sub compute
{
    my($ackermann, $m, $n) = @_;

    defined $ackermann->[$m][$n] or
      $ackermann->[$m][$n] = A($m, $n);

    $ackermann->[$m][$n]
}  

Расчет значения на C проще и эффективнее.

int A(int m, int n)
{
    if (m==0) return n+1;
    if (n==0) return A(m-1, 1);
    return A(m-1, A(m, n-1));
}

Вот XS-код для соединения подпрограммы C с модулем Perl

int
A(m, n)
        int m
        int n

После чего, мы можем расчитать значения таким образом

my $ackermann = new Math::Ackermann;

print $ackermann->compute(1, 1);  # расчет значения
print $ackermann->compute(2, 2);  # расчет значения
print $ackermann->compute(1, 1),  # возвращает закэшированное значение

В этом подходе, данные представлены на Perl, а расчеты выполняются на C. Это хорошее распределение ресурсов, потому что время доступа к данным меньше, по сравнению со временем выполнения расчетов. Также, объем данных очень мал: 2 числа на входе; одно число на выходе, следовательно эффективность хранилища не является проблемой.

Поскольку Math::Ackermann представляет пример, где данные расположены в Perl, то подпрограммы, которые написаны на Perl, такие как new() и compute(), имееют нативный доступ к этим данным. Подпрограммы, которые написаны на C, такие как A(), должны получать доступ к данным через Perl C API. Подпрограмма XS, показанная выше, описывает последовательность вызовов для A(), и xsubpp генерирует необходимые вызовы к Perl C API.

Далее мы увидем модуль Perl, который представляет данные в C.

Set::Bit

Set::Bit представляет множество чисел в промежутке 0..n. Он выделяет один бит в памяти для каждого числа в указанном диапазоне. Если бит установлен, тогда число принадлежит множеству; если бит очищен, тогда число не принадлежит множеству.

Вычисления, связанные с операциями над множеством тривиальные: установка и очистка битов для добавления и удаления чисел из множества; нахождение вхождений и пересечений через операции И и ИЛИ над битами. В следствии, общая производительность оценивается по скорости доступа к данным: сколько занимает времени нахождение определенного бита в определенном множестве. В дополнении, множества могут быть произвольно большими, таким образом эффективность хранилища неизбежно окажется под вопросом.

Чтобы увеличить производительность, мы представим объекты Set::Bit в виде структур на C

typedef struct
{
    int  nBits;  /* range of set: 0..nBits-1 */
    int  nInts;  /* number of integers       */
    int *pInts;  /* vector of integers       */
} Vector;

и мы напишем подпрограммы C для управления этими структурами

Vector *new      (int nBits);
void    DESTROY  (Vector *pVector);
void    dump     (Vector *pVector);
int     top      (Vector *pVector);
void    insert   (Vector *pVector, int n);
void    Remove   (Vector *pVector, int n);
int     member   (Vector *pVector, int n);
Vector *Union    (Vector *pA, Vector *pB);
Vector *intersect(Vector *pA, Vector *pB);

Имя Vector относится к вектору чисел (ints), которые хранят биты. Чтобы создать объект Set::Bit, мы вызываем new()

Vector *new(int nBits)
{
    Vector *pVector = (Vector *) malloc(sizeof (Vector));
    pVector->nBits = nBits;

    pVector->nInts = (nBits + sizeof(int) - 1) / sizeof(int);
    pVector->pInts = (int *) calloc(pVector->nInts, sizeof(int));

    return pVector;
}

Подпрограмма new()

  • выделяет память для структуры Vector
  • вычисляет количество чисел, необходимых для хранения всех битов в промежутке
  • выделяет массив чисел для хранения битов
  • возвращает адрес вектора Vector

Чтобы добавить число во множество, мы вызываем insert().

void insert(Vector *pVector, int n)
{
    int q, r;

    q = n / sizeof (int);
    r = n % sizeof (int);

    pVector->pInts[q] |= 1<<r;
}

Когда мы закончили работу со множеством, мы вызываем DESTROY(), чтобы освободить хранилище.

void DESTROY(Vector *pVector)
{
    free(pVector->pInts);
    free(pVector);
}

Другие подпрограммы имеют похожие реализации. Все объявления и определения на C можно найти здесь

Объект C

Мы установили A() как xsub в пакете Math::Ackermann, но объект Math::Ackermann является обыкновенным массивом Perl.

Мы установим подпрограммы C из vector.c как xsub’ы в пакете Set::Bit, но мы хотим сделать больше. Мы не желаем, чтобы объект Set::Bit был какой-либо разновидностью структурой данных Perl. Вместо этого, мы хотим, чтобы объектом Set::Bit являлась структура Vector на языке C.

Мы еще не писали модуль, в котором объектом являлась структура на C, и поэтому мы должны изучить некоторые новые трюки XS, чтобы заставить его работать. Если мы покажем полное решение сразу, оно наверняка покажется сложным и туманным. Вместо этого, мы найдем решение путём проб и ошибок.

h2xs

Как всегда, мы начнем с запуска h2xs

.../development>h2xs -A -n Set::Bit
.../development>cp vector.c Set/Bit/
.../development>cp vector.h Set/Bit/

Мы добавляем ключ OBJECT аргументом списка WriteMakefile() в Makefile.PL

'OBJECT'    => 'Bit.o vector.o'

Мы включаем vector.h в Bit.xs, и добавляем директиву PROTOTYPES. После этого Bit.xs выглядит как

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "vector.h"

MODULE = Set::Bit               PACKAGE = Set::Bit

PROTOTYPES: ENABLE
new

Так как мы не знаем, что делать дальше, то мы попытаемся написать подпрограмму XS для new() так

Vector *
new(nBits)
  int nBits

Теперь мы запускаем

.../development/Set/Bit>perl Makefile.PL
.../development/Set/Bit>make

и тут же получаем

Error: 'Vector *' not in typemap in Bit.xs, line 12

Что в переводе:

Ошибка: 'Vector *' не содержится в карте типов для Bit.xs, строка 12

Карта типов

Это несложно понять. make вызывает xsubpp, а xsubpp генерирует код для трансляции между представления Perl и C. Карта типов по-умолчанию в файле

/usr/local/lib/perl5/version/ExtUtils/typemap

содержит записи для трансляции многих встроенных типов данных C. Но Vector * не встроенный тип данных C, и карта типов по-умолчанию определенно не содержит записи для него.

Чтобы указать xsubpp как транслировать Vector *, мы создаем локальную карту типов в директории модуля, и добавляем запись в него для Vector *.

В самом общем случае, мы могли бы создать новый XS-тип, и добавить записи в секции TYPEMAP, INPUT, и OUTPUT. Если мы зовем наш XS-тип как T_VECTOR, то наша карта типов могла бы выглядеть таким образом

TYPEMAP
Vector *  T_VECTOR

INPUT
T_VECTOR
  # Код для трансляции скаляра Perl в Vector *

OUTPUT
T_VECTOR
  # Код для трансляции Vector * в скаляр Perl

T_PTROBJ

Но мы не станем всё это делать. Карта типов по-умолчанию уже содержит подходящий XS-тип, называемый T_PTROBJ, вместе с фрагментами кода для него в секциях INPUT и OUTPUT. Чтобы использовать T_PTROBJ, все что мы должны сделать это создать файл

.../development/Set/Bit/typemap

и записать строчку

Vector *    T_PTROBJ

в него. Это укажет xsubpp использовать фрагменты кода для T_PTROBJ во всех случаях, когда требуется транслировать [данные] между Vector * и скаляром Perl.

T_PTROBJ был создан специально для наших нужд: для создания структур C в объектах Perl. Если мы теперь запустим make, мы получим чистую компиляцию. Но прежде чем мы попытаемся запустить наш код, давайте взглянем на Bit.c и найдем, что xsubpp сделал (и не сделал) для нас.

sv_setref_pv

Вот клей-программа, созданная xsubpp чтобы присоединить new() к Perl

XS(XS_Set__Bit_new)
{
    dXSARGS;
    if (items != 1)
        croak("Usage: Set::Bit::new(nBits)");
    {
        int     nBits = (int)SvIV(ST(0));
        Vector *RETVAL;

        RETVAL = new(nBits);
        ST(0) = sv_newmortal();
        sv_setref_pv(ST(0), "VectorPtr", (void*)RETVAL);
    }
    XSRETURN(1);
}

Мы видели большую часть этого кода в прошлом месяце. Клей-подпрограмма извлекает nBits из стека Perl, и объявляет RETVAL как Vector *. Потом она вызывает new(), и присваивает значение возврата для RETVAL. Далее, она создает новый смертный (mortal) скаляр на стеке Perl для ST(0).

Теперь происходит магия: клей-подпрограмма вызывает sv_setref_pv, чтобы вернуть RETVAL в Perl.

sv_setref_pv(ST(0), "VectorPtr", (void*)RETVAL);

perlguts описывает sv_setref_pv следующим образом

SV* sv_setref_pv(SV* rv, char* classname, PV iv);

Копирует значение указателя (pointer value, PV) (адрес, не строку!) внутрь SV, чья ссылка это rv. SV ассоциирован (blessed) [с classname], если classname не равно null.

[Более простым языком, sv_setref_pv скопирует адрес третьего аргумента в первый, и при необходимости произведет ассоциирование с именем класса второго аргумента (bless). – прим. пер.]

SV сокращение для Scalar Value (значение скаляра); SV это внутренняя структура данных Perl, которая представляет скаляр. Когда мы вызываем sv_setref_pv(), она

  • создает новый SV
  • устанавливает значение SV равным RETVAL
  • ассоциирует (blesses) SV с пакетом VectorPtr
  • устанавливает значение смертного [объекта] на ST(0) равным ссылке на SV

Наконец, смертный [объект] в ST(0) это возвращаемое значение метода new().

Проблема пакета

Все это очень близко к тому, что мы хотим получить. Значение возврата new() это ссылка на ассоциированный скаляр (blessed scalar), который хранит указатель на нашу структуру на C. Однако, скаляр ассоциирован (blessed) с именем пакета VectorPtr, а не [именем] пакета Set::Bit.

Немного странно. Методы Perl, которые функционируют как конструкторы обычно возвращают ссылки на объекты, которые ассоциированны (blessed) с методами собственного пакета.

Строка

MODULE = Set::Bit               PACKAGE = Set::Bit

в Bit.xs устанавливает все наши xsub’ы в пакете Set::Bit. Но Set::Bit::new() возвращает объект ассоциированный с пакетом VectorPtr. Это означает, например, что мы можем написать

$set = Set::Bit::new(100);

и получить ссылку на правильный объект Perl, но мы не можем написать

$set->insert(42);

потому что $$set ассоциирован с именем пакета VectorPtr, а пакет VectorPtr не содержит метода insert(). Метод insert() находится внутри пакета Set::Bit, там же, где находится метод new().

Имеется несколько способов решения проблемы. Мы могли бы оставить new() в пакете Set::Bit, и добавить еще одну директиву PACKAGE, чтобы установить оставшиеся xsub’ы в пакете VectorPtr

MODULE = Set::Bit               PACKAGE = VectorPtr

Или мы могли бы переименовать весь модуль в VectorPtr.

Но эти решения уродливы и неудобные. Имена модулей не должны диктоваться структурами на C. Имена модулей должны быть свободными параметрами нашей затеи, выбранными чтобы лучше организовывать наши библиотеки и делать наш код легко-читаемым. Мы очень хотим получить наш объект ассоциированным (blessed) с пакетом Set::Bit.

$ntype

Как показано выше, код, который ассоциирует наш объект это

sv_setref_pv(ST(0), "VectorPtr", (void*)RETVAL);

Чтобы управлять пакетом, с которым наши объекты ассоциированы, мы должны понимать откуда появляется строка VectorPtr. Если мы посмотрим в секцию OUTPUT карты типов по-умолчанию, мы найдем запись

T_PTROBJ
        sv_setref_pv($arg, \"${ntype}\", (void*)$var);

Значит xsubpp генерирует код, который ассоциирует объект с пакетом через переменную $ntype. Из обсуждения в прошлом месяце, $ntype это тип переменной C, которая будет сконвертированна. В данном случае, переменная C это RETVAL, а её тип это Vector *.

Vector * это пример имени типа на C, который не является правильным именем пакета Perl. Чтобы учесть это, xsubpp молча заменяет строкой “Ptr” символ * перед тем, как присвоить значение для $ntype в качестве имени пакета. Это стандартный механизм: например, имя типа C Vector * * будет преобразовано в пакет Perl с именем VectorPtrPtr.

RETVAL

RETVAL это переменная C, которую объявляет xsubpp для хранения значения возврата нашей подпрограммы C; следовательно, её тип должен совпадать с типом возврата нашей подпрограммы C. xsubpp объявляет RETVAL с типом, который мы указали для нашей подпрограммы XS в Bit.xs. Таким образом, XS-код

Vector *
new(nBits)
  int nBits

в Bit.xs ведет к декларации на языке C

Vector *        RETVAL;

в Bit.c.

Это означает, что возвращаемый тип нашей подпрограммой XS определяется пакетом, с которым наш объект ассоциируется. Для того, чтобы ассоциировать наш объект с пакетом Set::Bit, мы должны написать нашу подпрограмму XS в виде

Set::Bit
new(nBits)
  int nBits

Кажется, что это не будет работать, потому что

  • Set::Bit не является возвращаемым типом подпрограммы new() на C
  • Set::Bit не является корректным именем типа в языке C

но мы можем обойти обе проблемы.

xsubpp отвечает за вторую проблему. Она заменяет двоеточия в $ntype на символы подчеркивания, когда он генерирует код для типа переменной C, используя для этого значение $ntype. Из этого следует, что XS-код показанный выше приведет к объявлению RETVAL в файле Bit.c следующим образом

Set__Bit        RETVAL;

что является корректным кодом на C.

Чтобы справиться с первой проблемой, мы просто добавим typedef в Bit.xs, как показано ниже

typedef Vector *Set__Bit;

xsubpp пропускает этот typedef неизменным в Bit.c. Теперь объявление RETVAL правильное и корректное.

xsubpp не заменяет двоеточия, когда генерирует код для имени пакета Perl, таким образом, вызов sv_setref_pv выглядит как

sv_setref_pv(ST(0), "Set::Bit", (void*)RETVAL);

Это ассоциирует наш объект с пакетом Set::Bit, именно так, как мы хотим.

новая карта типов

После проделанных изменений, Bit.xs выглядит вот так

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "vector.h"

typedef Vector *Set__Bit;

MODULE = Set::Bit               PACKAGE = Set::Bit

PROTOTYPES: ENABLE

Set::Bit
new(nBits)
        int nBits

Если мы сейчас запустим

.../development/Set/Bit>make

мы получим

Error: 'Set::Bit' not in typemap in Bit.xs, line 14

Что переводится как

Ошибка: 'Set::Bit' не найден в карте типов для Bit.xs, строка 14

Проблема в том, что xsubpp пытается найти возвращаемый тип нашей подпрограммы XS в карте типов. Мы создали запись в локальной карте типов для Vector *, но потом мы изменили тип возрата нашей подпрограммы XS с Vector * на Set::Bit. Нам нужно заменить запись в локальной карте типов на соответствующую

Set::Bit        T_PTROBJ

Синтаксис вызовов метода

С этим изменением, мы можем запустить

.../development/Set/Bit>make

и получить чистую компиляцию. Теперь, мы попытаемся создать объект Set::Bit. Мы добавляем эту строчку в test.pl

my $set = new Set::Bit 100;

и запускаем

.../development/Set/Bit>make test

Результат

1..1
ok 1
Usage: Set::Bit::new(nBits) at test.pl line 21.

ok 1 означает, что модуль загружен успешно. Сообщение об использовании означает, что мы вызвали метод new() с неверным количеством аргументов.

Немного подумав, мы можем идентифицировать проблему. Мы вызывали new() с синтаксисом вызова метода. В Perl, вызов метода

new Set::Bit 100

однозначно становится вызовом подпрограммы, где первый аргумент это имя пакета

Set::Bit::new("Set::Bit", 100)

Учитывая это, мы должны изменить нашу подпрограмму XS для передачи двух аргументов: строки и числа

Set::Bit
new(package, nBits)
        char *package
        int   nBits

Если мы запустим xsubpp для этой подпрограммы XS, она сгенерирует код на C с вызовом new() с обоими аргументами

RETVAL = new(package, nBits);

Мы могли бы изменить нашу подпрограмму C, чтобы получать оба аргумента, и после игнорировать имя пакета. Однако, я стараюсь избегать загрязнения моих интерфейсов с артифактами реализации. Вместо этого, мы добавим директиву CODE: в нашу подпрограмму XS, и напишем вызов new() самостоятельно.

CODE:
RETVAL = new(nBits);

Когда мы используем директиву CODE:, нам также нужна директива OUTPUT:, чтобы указать xsubpp возвращаемое значение

OUTPUT:
RETVAL

Наша подпрограмма XS теперь такая

Set::Bit
new(package, nBits)
        char *package
        int   nBits
        CODE:
        RETVAL = new(nBits);
        OUTPUT:
        RETVAL

Когда мы запускаем

.../development/Set/Bit>make test

мы получим чистую компиляцию, а результат таким

1..1
ok 1

показывающий, что наш вызов new() успешно выполнился.

Объект Set::Bit

Давайте отвлечемся на секунду и рассмотрим ситуацию. Клей-подпрограмма для new() вызывает sv_setref_pv(), а sv_setref_pv() создает скаляр Perl. Этот скаляр объект Set::Bit. Три важнейших факта, касающихся его

  • он имеет значение числа, которое является адресом структуры Vector
  • он ассоциирован с пакетом Set::Bit
  • Set::Bit::new() возвращает ссылку на него

Картина, которая иллюстрирует это

$set = new Set::Bit 100;

reference
Name: $set
RV: ----------->scalar
                Package: Set::Bit
                IV: ----------------->Vector
                                      {
                                      }

RV это сокращение для Reference Value (значение ссылки). IV – Integer Value (значение [целого] числа).

Поскольку у нас ссылка на объект Set::Bit, мы можем вызывать его методы. Когда мы вызываем метод, ссылка передается первым аргументом в метод.

Методы могут быть написаны либо на Perl, либо на C. Метод может разыменовывать свой первый аргумент, чтобы получить значение объекта. Значением объекта Set::Bit является машинный адрес структуры Vector.

В этом случае, не так много вещей, которые метод Perl может сделать с адресом структуры Vector, кроме как, наверное, напечатать его.

sub Set::Bit::print_address
{
    my $set = shift;
    printf "%p", $$set;
}

Метод C может передать адрес структуры Vector в любую подпрограмму C, определенную в vector.c. Давайте посмотрим как это сделано.

insert

Метод insert вставляет целое число во множество

$set = new Set::Bit 100;
$set->insert(42);

Вот XS-код, который устанавливает insert() в пакете Set::Bit

void
insert(pVector, n)
        Set::Bit pVector
        int      n

и код клей-подпрограммы, которую xsubpp генерирует, чтобы вызвать insert()

XS(XS_Set__Bit_insert)
{
    dXSARGS;
    if (items != 2)
        croak("Usage: Set::Bit::insert(pVector, n)");
    {
        Set__Bit        pVector;
        int     n = (int)SvIV(ST(1));

        if (sv_derived_from(ST(0), "Set::Bit")) {
            IV tmp = SvIV((SV*)SvRV(ST(0)));
            pVector = (Set__Bit) tmp;
        }
        else
            croak("pVector is not of type Set::Bit");

        insert(pVector, n);
    }
    XSRETURN_EMPTY;
}

Давайте взглянем на код C, чтобы посмотреть как мы получаем наш Vector * из Perl.

Подпрограмма XS объявляет pVector как тип Set::Bit. Как и раньше, xsubpp заменяет двоеточия на символы подчеркивания за нас, генерируя объявление на C

Set__Bit        pVector;

и typedef в Bit.xs создает псевдоним (alias) Set__Bit для Vector *.

Первый аргумент insert() это ссылка на объект Set::Bit. Этот аргумент появляется на стеке Perl как ST(0).

sv_derived_from() проверяет, что ST(0) на самом деле является ссылкой на объект Set::Bit. SvRV() разыменовывает ST(0), возвращая скаляр, который был создан ранее sv_setref_pv(). SvIV() извлекает значение этого скаляра как Integer Value (IV). Это значение является Vector *, который был сохранен с помощью sv_setref_pv().

Клей-подпрограмма сохраняет IV в [переменной] tmp, и после присваивает ей [значение] pVector на следующей строчке, используя приведение типов, чтобы заткнуть компилятор. Наконец, она вызывает подпрограмму на C insert(), передавая ей Vector * в pVector.

Методы экземляра

XS-код для оставшихся методов экземпляра похожий. Вот member()

int
member(pVector, n)
        Set::Bit pVector
        int      n

и intersect()

Set::Bit
intersect(pA, pB)
        Set::Bit pA
        Set::Bit pB

В каждом случае, первый аргумент это ссылка на объект, который вызвал метод. intersect() создает и возвращает новый объект Set::Bit; механика этому действию аналогична описанному выше для new().

union

XS-код для union() немножко отличается. Мы не можем назвать подпрограмму C под именем union, потому что union является зарезервированным словом в C. Я назвал подпрограмму Union, но я хочу, чтобы Perl метод назывался union, для того, чтобы интерфейс Perl имел последовательную схему именования методов. Написав немножко XS-кода, мы можем назвать и вызывать наш метод.

Set::Bit
union(pA, pB)
        Set::Bit pA
        Set::Bit pB
        CODE:
        RETVAL = Union(pA, pB);
        OUTPUT:
        RETVAL

Мы назвали подпрограмму XS union, и после добавили директиву CODE:, чтобы вызвать Union в C.

DESTROY

Когда последняя ссылка на объект Set::Bit исчезает, Perl удаляет объект. Объект из себя представляет скаляр Perl: Perl выделил место в памяти под него из вызова sv_setref_pv(), и Perl высвободит ту память.

Объект Set::Bit хранит указатель на структуру Vector. Perl не знает ничего о структуре Vector. Мы выделили место для нее, и мы ответственны за её освобождение. Мы делаем это в методе DESTROY.

Perl вызывает метод DESTROY для нас после того как последняя ссылка на объект Set::Bit исчезает, и перед тем, как удаляется объект Set::Bit. Не смотря на то, что Perl вызывает DESTROY() автоматически, это обычный метод. Вот XS-код для DESTROY().

void
DESTROY(pVector)
        Set::Bit pVector

Также как и с другими методами экземпляра, клей-подпрограмма извлекает Vector * из стека Perl и передает его нашей подпрограмме DESTROY() на C. Наш код на C после этого освождает (free) память Vector.

Методы Perl

Мы упомянули выше, что метод Perl не может оперировать Vector *. Однако, мы можем использовать методы-акцессоры (написанные на C), чтобы получить данные экземпляра из объекта, а потом оперировать этими данными в Perl.

member() это метод-акцессор: он возвращает true, если его аргумент элемент множества. С помощью метода member(), мы можем писать другие методы в Perl. Например, метод elements() возвращает список своих элементов множества

sub elements
{
    my $set = shift;
    grep { $set->member($_) } 0..$set->top
}

и метод print() возвращает строку, содержащую представление множества для печати

sub print
{
    my $set = shift;
    join ", ", $set->elements
}

Учитывая подходящий набор методов доступа, выбор, когда использовать реализацию на C или Perl, можно определить исходя из требований к производительности и удобства.

Объект Perl

Ранее мы сказали, что мы хотим, чтобы объект Set::Bit был структурой C Vector, скорее, чем объектом данных Perl. Это не сработало таким образом. Объект Set::Bit на самом деле объект данных Perl: он скаляр, созданный sv_setref_pv().

Тем не менее, объект Set::Bit дает нам необходимую функциональность объекта C

  • данные представлены в C
  • мы можем писать методы на C
  • методы, написанные на C получают данные экземпляра через Vector *, переданный в качестве первого аргумента

В то же время, объект Set::Bit дает нам гибкость для написания методов на Perl. Это, наверное, лучшее из двух миров.

Дистрибутив

Ниже даны исходники и дистрибутив для Math::Ackermann

А вот исходники и дистрибутив Set::Bit

Align::NW

Модуль Align::NW выполняет глобальное оптимальное выравнивание последовательностей Нидлмана-Вунша. Он содержит данные и в Perl и в C, и методы, написанные и на Perl, и на C. Мы обсудим техники, использованные для его создания – в следующем месяце.


Заметки

путём проб и ошибок

Именно так я узнал об этом.

создает

Документация не достаточно ясно объясняет этот момент.

обойти обе проблемы

Mr. E.V. Lambert of Homeleigh, The Burrows, Oswestly, has presented us with a poser. We do not know which bush he is behind, but we can soon find out.

BOOM

BOOM

BOOM <yyaaahhhhh!>

Yes, it was the middle one.

– Monty Python, How Not to be Seen

загружен успешно

что никогда не следует воспринимать как должное, когда пишутся модули XS

Сообщение об использовании

Мы получаем сообщения об использовании потому что включили прототипы в Bit.xs.

значение числа

как противополжность строковому значению