Fortran语言中的高效z阶变换

Fortran语言中的高效z阶变换,fortran,bit-manipulation,z-order,Fortran,Bit Manipulation,Z Order,对于我目前在网格生成算法方面的工作,我需要一种有效的方法将三维坐标转换为z阶(更准确地说,三个4字节整数转换为一个8字节整数),反之亦然。这篇维基百科文章对它进行了很好的描述: . 由于我不是一名程序员,我提出的解决方案实现了它应该做的事情,但使用mvbits内在特性显式执行位交错可能非常幼稚: SUBROUTINE pos_to_z(i, j, k, zval) use types INTEGER(I4B), INTENT(IN) :: i, j, k INTEGER(I8B), INT

对于我目前在网格生成算法方面的工作,我需要一种有效的方法将三维坐标转换为z阶(更准确地说,三个4字节整数转换为一个8字节整数),反之亦然。这篇维基百科文章对它进行了很好的描述: . 由于我不是一名程序员,我提出的解决方案实现了它应该做的事情,但使用mvbits内在特性显式执行位交错可能非常幼稚:

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中更有效地进行位交错,那将非常有帮助。

使用类似于所述的查找表,可以优化从3个共字到z顺序的转换。由于您只使用输入值的20位,因此使用包含1024个条目而不是256个条目的查找表(足以索引10位)将更为有效,这样您只需为3个输入值中的每一个执行2个查找,并针对交错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个值用作数组的索引,并使用移位和加法组合查找的值以将这些值交错在一起。在这种情况下,加法相当于按位或运算,因为在每个位位置最多设置一位的情况下,不会有任何进位。因为在表中的值中只能设置每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次查找,每次为三个20位值中的每个值获取5位。屏蔽底部15位,然后右移0、1和2位,以获得k、j和i的查找索引。然后移位和掩码得到位15-29、30-44和45-59,每次都做同样的操作,移位和相加以重构k,j和i.

您这样做是出于某种原因,而不是直接存储整数坐标?这两个子例程的主要目的之一是在人口稀少的笛卡尔网格中查找邻居。我不知道存储整数坐标对我有什么帮助。你能说得更具体一点吗?谢谢,那确实快多了。再仔细考虑一下我的算法,反正也不需要做逆变换(z顺序到位置)。我想我应该仔细阅读逐位运算,但现在我的问题已经解决了。让我们假设我想使用8字节z顺序的全部正范围。我要做的是:1)在pos_to_z例程结束时放弃移位+1 2)预先计算morton表中的2048个值,而不是3)将值“-10”更改为“-11”,并移位33,34,35,而不是30,31,32。如果为morton表存储2048个值不是问题,我可以坚持每个坐标只移动2次,对吗?现在有3个输入值,每个值使用20位创建60位值,存储在8个字节中。不能使用完整的8字节,因为64位不能被3整除。但是,如果您想将每个值的21位存储在63位中,那么您所说的听起来是正确的。你会像现在一样做底部的10位,然后做顶部的11位。除了您所描述的之外,您还需要将iand掩码值从1023更改为2047,即“全正范围”,实际上,我指的是z阶变量的63位。谢谢你的澄清。