【问题标题】:Optimizing quadrant selection优化象限选择
【发布时间】:2014-03-24 03:28:58
【问题描述】:

我正在研究一种将项目细分为象限的数据结构,我发现的瓶颈之一是我选择该点象限的方法。诚然,它相当简单,但它被调用了很多次,以至于它加起来。我想一定有一种有效的方法可以把它弄成我想要的东西,但我想不出。

private int Quadrant(Point p)
{
    if (p.X >= Center.X)
        return p.Y >= Center.Y ? 0 : 3;
    return p.Y >= Center.Y ? 1 : 2;
}

Center 的类型为 Point,坐标为 ints。是的,我已经运行了一个代码配置文件,不,这不是过早的优化。


因为这仅在内部使用,所以我想我的象限必须Cartesian order,只要它们的范围是 0-3。

【问题讨论】:

    标签: c# optimization bit-manipulation


    【解决方案1】:

    C/C++ 中最快的方法是

    (((unsigned int)x >> 30) & 2) | ((unsigned int)y >> 31)
    

    (30/31 或 62/63,取决于 int 的大小)。 这将按 0、2、3、1 的顺序给出象限。

    为 LBushkin 编辑:

    (((unsigned int)(x - center.x) >> 30) & 2) | ((unsigned int)(y-center.y) >> 31)
    

    【讨论】:

    • 这不考虑构建象限的任意中心点。
    • 我已经完成了我正在处理的结构。感谢您的评论,但我无法大幅缩短时间,所以我坚持使用我原来的实现。如果您想对我的课程发表评论,可以在此答案中找到:stackoverflow.com/questions/3142919/…
    • 好问题和好答案。谢谢!
    【解决方案2】:

    我不知道您是否可以在 C# 中使这段代码显着加快。但是,您可以做什么,它会查看您如何处理点,并查看您是否可以避免对该方法进行不必要的调用。也许您可以创建一个QuadPoint 结构来存储一个点所在的象限(在您计算一次之后),这样您就不必再这样做了。

    但是,诚然,这取决于您的算法在做什么,以及是否可以存储/记忆象限信息。如果每个点都是完全唯一的,这显然无济于事。

    【讨论】:

      【解决方案3】:

      我刚刚被告知产生正确排序的 0、1、2、3 象限结果的解决方案:

      #define LONG_LONG_SIGN (sizeof(long long) * 8 - 1)
      
      double dx = point.x - center.x;
      double dy = point.y - center.y;
      
      long long *pdx = (void *)&dx;
      long long *pdy = (void *)&dy;
      
      int quadrant = ((*pdy >> LONG_LONG_SIGN) & 3) ^ ((*pdx >> LONG_LONG_SIGN) & 1);
      

      此解决方案适用于 double 类型的 x,y 坐标。

      我已经对该方法和原始问题中的分支方法进行了一些性能测试:我的结果是 分支方法总是快一点(目前我有稳定的 160/180 关系),所以我更喜欢分支方法而不是按位运算的方法。


      更新

      如果有人感兴趣,所有三个算法都被合并到EKAlgorithms C/Objective-C repository 中作为“笛卡尔象限选择”算法:

      1. 原始分支算法
      2. @ruslik 的按位算法来自已接受的答案。
      3. 我的一位同事提出的按位替代方案,它比第二种算法慢一点,但以正确的顺序返回象限。

      那里的所有算法都经过优化,可以处理双类型点。

      性能测试表明,一般来说,第一个分支算法在 Mac OS X 上是赢家,但在 Linux 机器上,我们确实看到第二个算法的执行速度比分支算法快一点。 p>

      因此,一般结论是坚持使用分支算法,因为按位版本不会带来任何性能提升。

      【讨论】:

        【解决方案4】:

        我的第一次尝试是摆脱嵌套条件。

        int xi = p.X >= Center.X ? 1 : 0;
        int yi = p.Y >= Center.Y ? 2 : 0;
        int quadrants[4] = { ... };
        return quadrants[xi+yi];
        

        如果允许对象限重新编号,则在象限中查找数组是可选的。我的代码仍然需要两个比较,但它们可以并行完成。

        对于任何 C# 错误,我提前道歉,因为我通常编写 C++。


        当两个无符号 31 位坐标存储在一个 64 位无符号长变量中时,可能会更有效。

        // The following two lines are unnecessary
        // if you store your coordinated in unsigned longs right away
        unsigned long Pxy = (((unsigned long)P.x) << 32) + P.y;
        unsigned long Centerxy = (((unsigned long)Center.x) << 32) + Center.y;
        
        // This is the actual calculation, only 1 subtraction is needed.
        // The or-ing with ones hast only to be done once for a repeated use of Centerxy.
        unsigned long diff = (Centerxy|(1<<63)|(1<<31))-Pxy;
        int quadrant = ((diff >> 62)&2) | ((diff >> 31)&1);
        

        退后一步,可能会有不同的解决方案。不要立即将您的数据结构分成象限,而是在两个方向上交替排列。这也是在相关的Kd-tree

        【讨论】:

        • 我怀疑这会快得多。您执行相同数量的条件检查,但您还添加了一个数组查找。代码可能更简洁,但您必须对其进行分析以证明它更快。
        • int xi = (int)(p.X >= Center.X); int yi = (int)(p.Y >= Center.Y) * 2;肮脏的方式?
        • @arul:您不能将 bool 强制转换为 C# 中的任何数字类型。这不会编译。
        • @arul 如果更改为编译版本,这与启用的优化应该没有任何区别。
        猜你喜欢
        • 2012-11-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-16
        • 2011-04-11
        • 2016-06-09
        • 2011-08-27
        • 2011-06-09
        相关资源
        最近更新 更多