Документация Engee

Совместимость с HDF5

Библиотека JLD2 основана на спецификации формата HDF5 и создает файлы, совместимые с официальной библиотекой C HDF5.

Преимуществом является совместимость с другими библиотеками, использующими HDF5, такими как оболочка Julia HDF5.jl или даже пакет h5py для Python. Помимо этого, соблюдение стандартов HDF5 позволяет использовать инструменты интроспекции файлов, такие как h5dump и h5debug, предоставляемые группой HDF5.

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

Основные сведения о кодировании структур (struct) Julia

Стандарт HDF5 поддерживает так называемые составные типы данных (compound datatypes), которые состоят из набора известных типов данных. Они очень похожи на структуры (struct) в Julia. Если пользователь хочет записать на диск данные нестандартного типа, JLD2 создает соответствующий составной тип и сохраняет его в файле. Все определения пользовательских типов в файле JLD2 хранятся в группе _types/. Поэтому определения типов достаточно записать в файл один раз, после чего все экземпляры структуры (struct) будут ссылаться на соответствующее определение.

Пример

julia> using JLD2

julia> struct MyCustomStruct
       x::Int64
       y::Float64
       end

julia> @save "test.jld2" a=MyCustomStruct(42, π)

Давайте посмотрим, как JLD2 представит простой пример структуры MyCustomStruct. Для этого мы изучим выходные данные h5dump.

$> h5dump test.jld2
HDF5 "test.jld2" {
GROUP "/" {
   GROUP "_types" {
      DATATYPE "00000001" H5T_COMPOUND {
         H5T_STRING {
            STRSIZE H5T_VARIABLE;
            STRPAD H5T_STR_NULLPAD;
            CSET H5T_CSET_UTF8;
            CTYPE H5T_C_S1;
         } "name";
         H5T_VLEN { H5T_REFERENCE { H5T_STD_REF_OBJECT }} "parameters";
      }
         ATTRIBUTE "julia_type" {
            DATATYPE  "/_types/00000001"
            DATASPACE  SCALAR
            DATA {
            (0): {
                  "Core.DataType",
                  ()
               }
            }
         }
      DATATYPE "00000002" H5T_COMPOUND {
         H5T_STD_I64LE "x";
         H5T_IEEE_F64LE "y";
      }
         ATTRIBUTE "julia_type" {
            DATATYPE  "/_types/00000001"
            DATASPACE  SCALAR
            DATA {
            (0): {
                  "Main.MyCustomStruct",
                  ()
               }
            }
         }
   }
   DATASET "a" {
      DATATYPE  "/_types/00000002"
      DATASPACE  SCALAR
      DATA {
      (0): {
            42,
            3.14159
         }
      }
   }
}
}

Как видите, на верхнем уровне файл содержит два компонента. Это набор данных "a" (то, что мы хотели сохранить) и группа _types, в которой помещается вся необходимая информация о типах.

Можно заметить, что библиотека JLD2 сохранила два составных типа данных. Первый — это Core.Datatype, что на первый взгляд может показаться странным. Его назначение в том, чтобы сообщить HDF5, как выглядит сериализованный тип данных Julia (имя и список параметров).

Далее следует определение MyCustomStruct с двумя полями: H5T_STD_I64LE "x" и H5T_IEEE_F64LE "y", которые определяют целочисленное поле x и поле с плавающей запятой y.

Примечание по указателям

В языке программирования Julia необходимость в указателях (Ptr) возникает нечасто. Однако если имеются двоичные зависимости и требуется постоянно передавать области памяти, указатели становятся важны. Указатели — это адреса участков в памяти, и после завершения работы программы они утрачивают свой смысл.

В принципе, хранить указатель на файл особого смысла нет, но для более последовательной работы JLD2, аналогично модулю Base.Serialization, автоматически принимает указатели. Это полезно при сохранении больших структур, например объекта решения DifferentialEquations.jl, в котором может быть указатель. При десериализации экземпляры полей указателей создаются как нулевые указатели.

Это делается с помощью всего трех строк кода, в которых используется пользовательская логика сериализации, и они приводятся здесь, так как являются хорошим примером применения данной функции.

   writeas(::Type{<:Ptr}) = Nothing
   rconvert(::Type{Ptr{T}}, ::Nothing) where {T} = Ptr{T}()

Как правило, обычно также необходимо определить метод для wconvert. Однако в данном случае JLD2 определяет, что для создания nothing явного преобразования не требуется.