Эффективно оборачивайте тривиальные типы в swig

Вопрос или проблема

Предположим, у меня есть очень легковесный тип в C++, например:

/// Представляет временную метку UTC
class Timestamp {
    int64_t nanos_;

    public:
    Timestamp(int64_t nanos)
        : nanos_(nanos) {}

    int64_t nanos() const noexcept { return nanos_; }

    /// Преобразовать временную метку в строку формата YYYY-MM-DD HH:MM:SS (время UTC)
    std::string toString() const;

    /// Преобразовать временную метку в строку формата YYYY-MM-DD HH:MM:SS (время UTC)
    /// Использовать указанную временную зону, например, 'America/New_York'
    std::string toString(std::string const& tz) const;

    // Другие функции...
};

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

Я хочу, чтобы этот класс был доступен в swig, но я также хочу, чтобы он был представлен эффективным образом.

В C++ это тривиально копируемый класс. Нет накладных расходов на его использование по сравнению с прямыми операциями над int64_t, и манипуляции большими векторами временных меток тривиальны.

Сгенерированная обертка swig значительно тяжелее в сравнении! Каждое отдельное вхождение Timestamp в C# несет указатель на отдельную временную метку C++, выделенную в куче, и каждый вызов функции сопровождается множеством уровней косвенной адресации!

///  Представляет временную метку UTC

public class Timestamp : global::System.IDisposable {
  private global::System.Runtime.InteropServices.HandleRef swigCPtr;
  protected bool swigCMemOwn;

  internal Timestamp(global::System.IntPtr cPtr, bool cMemoryOwn) {
    swigCMemOwn = cMemoryOwn;
    swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
  }

  internal static global::System.Runtime.InteropServices.HandleRef getCPtr(Timestamp obj) {
    return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
  }

  internal static global::System.Runtime.InteropServices.HandleRef swigRelease(Timestamp obj) {
    if (obj != null) {
      if (!obj.swigCMemOwn)
        throw new global::System.ApplicationException("Невозможно освободить собственность, так как память не принадлежит");
      global::System.Runtime.InteropServices.HandleRef ptr = obj.swigCPtr;
      obj.swigCMemOwn = false;
      obj.Dispose();
      return ptr;
    } else {
      return new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
    }
  }

  // ...

Есть ли способ убедить swig создать что-то столь же эффективное в C# (например, обертка типа значения над int64?)

Ответ или решение

Эффективная обертка тривиальных типов в SWIG

Когда речь идет о работе с C++ в среде C#, использование SWIG (Simplified Wrapper and Interface Generator) может создать некоторые интересные проблемы, особенно в отношении управления памятью и производительности. Одна из самых распространенных проблем заключается в том, что SWIG по умолчанию создает "тяжелые" обертки для классов C++, что может быть неэффективно для тривиально копируемых типов, таких как наш пример с классом Timestamp.

Проблема с производительностью

В вашем случае, класс Timestamp является легковесной оберткой над int64_t, что делает его тривиально копируемым. Однако, при использовании SWIG, каждый экземпляр класса Timestamp в C# становится ссылкой на отдельный экземпляр C++ класса, что приводит к множественным уровням косвенности, снижая общую производительность. Это создает избыточные накладные расходы как на память, так и на производительность при взаимодействии с C++.

Решение проблемы с помощью структур

Для того чтобы обернуть наш класс Timestamp более эффективно в C#, можно изменить способ, которым SWIG генерирует код. Вместо того чтобы создавать класс в C#, мы можем использовать структуры. В C# структуры — это типы значения, и они хранятся в стеке, что приведет к значительном снижению накладных расходов, связанных с управлением памятью.

Чтобы реализовать это с помощью SWIG, вам нужно будет внести следующие изменения:

  1. Создание обертки в SWIG:

    При помощи директивы %typemap, мы можем указать, как SWIG должен обрабатывать ваш класс. Например, можно добавить следующее содержимое в ваш интерфейс SWIG:

    %typemap(in) Timestamp {
       $1 = new Timestamp($input);
    }
    
    %typemap(out) Timestamp {
       $result = (long long)$1.nanos();
    }

    Тем не менее, чтобы полностью использовать структуру, вам может потребоваться создать дополнительный код, который явно будет представлять Timestamp как структуру.

  2. Объявление структуры в C#:

    Вместо генерируемого класса, вы можете создать структуру:

    [StructLayout(LayoutKind.Sequential)]
    public struct Timestamp {
       private long nanos;
    
       public Timestamp(long nanos) {
           this.nanos = nanos;
       }
    
       public long Nanos => nanos;
    
       public string ToString() {
           // Вызов соответствующего C++ метода для конвертации
       }
    
       public string ToString(string tz) {
           // Вызов соответствующего C++ метода с учётом временной зоны
       }
    }
  3. Интеграция структуры с кодом C++:

    Необходимо создать соответствующий механизм для передачи структур между C# и C++. Это может включать создание методов, которые могут напрямую манипулировать вашей структурой без создания дополнительных экземпляров на куче.

Заключение

Использование структур вместо классов для тривиально копируемых типов — это один из самых эффективных способов уменьшить накладные расходы на память в SWIG. Так как ваш класс Timestamp является легковесным, это решение поможет сохранить как производительность, так и удобство использования. Внедрение данной методологии даст возможность сохранить преимущества C++ с его исключительной производительностью и в то же время обеспечит удобство работы с ним на стороне C#.

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

Оцените материал
Добавить комментарий

Капча загружается...