Fortran

时间:2015-05-25 10:39:03

标签: fortran bit-manipulation z-order

对于我目前关于网格生成算法的工作,我需要一种有效的方法将三维坐标转换为z顺序(更准确地说:三个4字节整数转换为一个8字节整数),反之亦然。这篇维基百科的文章描述得相当不错: Z-order curve。 由于我不是程序员,我提出的解决方案做了它本应该做的事情,但是使用mvbits inner来明确地做位交错可能很天真:

SUBROUTINE pos_to_z(i, j, k, zval)

use types

INTEGER(I4B), INTENT(IN)  :: i, j, k
INTEGER(I8B), INTENT(OUT) :: zval
INTEGER(I8B) :: i8, j8, k8
INTEGER(I4B) :: b

zval = 0
i8 = i-1
j8 = j-1
k8 = k-1

do b=0, 19
    call mvbits(i8,b,1,zval,3*b+2)
    call mvbits(j8,b,1,zval,3*b+1)
    call mvbits(k8,b,1,zval,3*b  )
end do

zval = zval+1

END SUBROUTINE pos_to_z


SUBROUTINE z_to_pos(zval, i, j, k)

use types

INTEGER(I8B), INTENT(IN)  :: zval
INTEGER(I4B), INTENT(OUT) :: i, j, k
INTEGER(I8B) :: i8, j8, k8, z_order
INTEGER(I4B) :: b

z_order = zval-1
i8 = 0
j8 = 0
k8 = 0

do b=0, 19
    call mvbits(z_order,3*b+2,1,i8,b)
    call mvbits(z_order,3*b+1,1,j8,b)
    call mvbits(z_order,3*b  ,1,k8,b)
end do

i = int(i8,kind=I4B) + 1
j = int(j8,kind=I4B) + 1
k = int(k8,kind=I4B) + 1

END SUBROUTINE z_to_pos

请注意,我更喜欢输入和输出范围以1而不是0开头,从而导致一些额外的计算。 事实证明,这种实现相当缓慢。我测量了转换和重新转换10 ^ 7个位置所需的时间:
gfortran -O0:6.2340秒
gfortran -O3:5.1564秒
ifort -O0:4.2058秒
ifort -O3:0.9793秒

我还为gfortran尝试了不同的优化选项但没有成功。虽然使用ifort的优化代码已经快得多,但它仍然是我程序的瓶颈。 如果有人能指出我如何在Fortran中更有效地进行位交错,那将会非常有用。

1 个答案:

答案 0 :(得分:1)

使用类似于here描述的查找表,可以优化从3个co-ords到z-order的转换。由于您只使用了20位的输入值,因此使用具有1024个条目而不是256个条目的查找表会更有效,足以索引10位,因此您只需对每个条目进行2次查找您的3个输入值,并针对交错3个值而不是2个值进行了修改。

数组的 n 条目存储整数 n ,其位扩展,使位0位于位0,位1移至位3,位2移动到位6,依此类推,所有剩余的位都设置为零。查找表数组可以像这样初始化:

subroutine init_morton_table(morton_table)
    integer(kind=8), dimension (0:1023), intent (out) :: morton_table
    integer :: b, v, z
    do v=0, 1023
        z = 0
        do b=0, 9
            call mvbits(v,b,1,z,3*b)
        end do
        morton_table(v) = z
    end do
end subroutine init_morton_table

要实际交错这些值,将3个输入值分成低10位和高10位,然后使用这6个值作为数组的索引,并使用移位组合查找值并添加交错值一起。在这种情况下,加法等效于按位OR运算,因为在每个比特位置最多只能设置一个比特,因此不会有任何进位。因为只能在表中的值中设置每个第3位,所以将其中一个值偏移1位而将另一个值偏移2意味着不会发生任何冲突。

subroutine pos_to_z(i, j, k, zval, morton_table)
    integer, intent(in) :: i, j, k
    integer(kind=8), dimension (0:1023), intent (in) :: morton_table
    integer(kind=8), intent (out) :: zval
    integer(kind=8) :: z, i8, j8, k8

    i8 = i-1
    j8 = j-1
    k8 = k-1

    z = morton_table(iand(k8, 1023))
    z = z + ishft(morton_table(iand(j8, 1023)),1)
    z = z + ishft(morton_table(iand(i8, 1023)),2)
    z = z + ishft(morton_table(iand(ishft(k8,-10), 1023)),30)
    z = z + ishft(morton_table(iand(ishft(j8,-10), 1023)),31)
    zval = z + ishft(morton_table(iand(ishft(i8,-10), 1023)),32) + 1

end subroutine pos_to_z

你可以使用类似的技术走另一条路,但我认为它不会那么有效。创建一个32768值(15位)的查找表,用于存储5位重构输入值。您将需要进行12次查找,每次为3个20位值获取5位。屏蔽底部的15位,然后向右移动0,1和2位以获得k,j和i的查找索引。然后移位并屏蔽以获得位15-29,30-44和45-59并且每次都执行相同的操作,移位并添加以重建k,j和i。