ODC库编程实践
前面提到过odb-2数据格式看成是二维表格型数据,但表格中每列的数据类型可以不同。开发人员可能考虑到通用性以及读写性能,在实现时将表格数据统一处理成双精度的二维数组(也可以看成更一般的一维数组)。
real(8), target :: data(ncols, nrows)
rc = encoder%set_data(data, column_major)
此时需要解决两个问题:
- 二维数组在内存中的布局,即以列主元还是行主元的内存布局。
- 如果column的类型不是双精度,那么就存在数据类型转换,对于数值类型还算简单,但字符类型如何处理?
第一个问题,我们从ODC API看起。
C API odc_encoder_set_data_array
函数用于设置编码的输入数组。函数第二个参数是指定的输入数组,这里使用const void*
类型符,通用类型指针。第三个参数是指数组(表格)的宽度,以字节为单位。第四个参数是数组的行数。最后一个参数是指列主元布局每列宽度,以字节为单位,一般为8字节,如果是行主元,传入0值。
对应的Fortran API,则是类对象encoder的成员函数set_data
,查看接口实现,可以看出实际上调用的是encoder_set_data_array
函数,进而调用C API odc_encoder_set_data_array
。其实现如下所示:
function encoder_set_data_array(encoder, data, column_major) result(err)
class(odc_encoder), intent(inout) :: encoder
real(dp), intent(in), target :: data(:,:)
logical, intent(in), optional :: column_major
integer(c_long) :: width, height
integer(c_int) :: columnMajorWidth
integer :: err
logical(c_bool) :: l_column_major = .true.
if (present(column_major)) l_column_major = column_major
if (l_column_major) then
width = size(data, 2) * double_size
height = size(data, 1)
columnMajorWidth = 8
else
width = size(data, 1) * double_size
height = size(data, 2)
columnMajorWidth = 0
end if
err = odc_encoder_set_data_array(encoder%impl, c_loc(data), width, height, columnMajorWidth)
end function
Fortran API接口简化很多,根据参数column_major
确定表格数组的高度、宽度。
- column_major = .false.,即行主元局部:一行内每列数据在内存中连续。对于Fortran语言,低维变化最快。所以一般声明数组为
data(ncols,nrows)
。表格宽度为ncols*double_size
字节,高度为nrows
。 - column_major = .true.,即列主元布局:一列内每行数据在内存中连续。对于Fortran语言一般声明
data(nrows,ncols)
,其中第1维元素在内存中连续,代表了row连续,其高度为nrows,而宽度是ncols*double_size
字节。
那么对于Fortran程序调用ODC库处理odb-2数据,哪种内存布局更好?
我认为行主元内存布局更佳。前面介绍过对于超过8字节宽度的字符变量会导致字符串在内存中占据多列。在列主元内存布局下如果一列放不下所有字符串,跨越多个列,但列与列之间在内存中不连续,访存效果很变差。此外,处理观测数据一般是对一条一条观测记录(行)处理的,保证一条记录(行)内内存连续可能会更好。
第二个问题的解决方法是通过Fortran内置函数transfer
进行二进制位级的拷贝。
比如对于长度为5的字符类型转换为双精度浮点型
character(5) :: expver_str
! loop for nrows:
write(expver_str, '(a,a)') 'xxxx', '' // c_null_char
data(1, i) = transfer(expver_str, data(1, i)) ! expver
因为要传递给C函数,字符类型变量要添加终止符c_null_char
。
对于跨越多列的字符串,
character(16) :: wigos_str
! loop for nrows:
write(wigos_str, '(a,i0.2,a)') '0-12345-0-678', i - 1, '' // c_null_char
data(4:5, i) = transfer(wigos_str, data(4:5, i)) ! wigos@hdr

