【问题标题】:Designing this algorithm a better way?以更好的方式设计此算法?
【发布时间】:2011-01-22 22:25:59
【问题描述】:

我正在处理一个更复杂的版本(车辆在 X 和 Y 方向上移动)

我制作这个例子是为了获得更好的方法来实现这一点。

  1. 我有一辆车在 X 方向上以一定速度 (24.5872 mps) 行驶
  2. 我通过使用执行器每 100 毫秒增加 X 值来模拟这一点(以保持其 X 位置更准确和实时)
  3. 每一秒后,我都会向另一个进程发送一条消息,其中包含我刚刚覆盖的行的 xMin 和 xMax 值
  4. 如果前一个 X 区域中存在“坑洞”(消息回调消息到linkedblockingqueue),其他进程将响应一条 JMS 消息(通常是立即)告诉我停止。

我遇到的问题是“通常立即”部分。如果我没有足够快地得到响应,我认为它会影响我算法的整个时间。有什么更好的方法来处理这种情况?

这是我正在尝试做的一些基本代码:

public class Mover implements MessageHandler {

    private static final long CAR_UPDATE_RATE_IN_MS = 100;
    private static double currX = 0;
    private static double CONSTANT_SPEED_IN_MPS = 24.5872; // 55 mph
    private static double increment = CONSTANT_SPEED_IN_MPS / (1000 / CAR_UPDATE_RATE_IN_MS);
    static LinkedBlockingQueue<BaseMessage> messageQueue = new LinkedBlockingQueue<BaseMessage>(); // ms

    private static int incrementor = 0;

    public static void main(String[] args) {
        startMoverExecutor();
    }

    private static void startMoverExecutor() {

        ScheduledExecutorService mover = Executors.newSingleThreadScheduledExecutor();
        mover.scheduleAtFixedRate((new Runnable() {

            @Override
            public void run() {
                currX = incrementor * increment;

                if (incrementor % (1000 / CAR_UPDATE_RATE_IN_MS) == 0) {
                    System.out.println(currX);

                    sendMessage(currX - CONSTANT_SPEED_IN_MPS, currX);

                    // do something
                    try {
                        messageQueue.poll(1000, TimeUnit.MILLISECONDS);

                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                }
                incrementor++;
            }

        }), 0, CAR_UPDATE_RATE_IN_MS, TimeUnit.MILLISECONDS);

    }

    @Override
    public void handleMessage(BaseMessage msg) {
        messageQueue.add(msg);

    }

    protected static void sendMessage(double firstX, double secondX) {
        // sendMessage here

    }

}

【问题讨论】:

  • 您使用 JMS 消息有什么原因吗?
  • 这是您代码中的一个小加速:将 currX = incrementor * increment; 更改为 currX += increment; 并将初始化 currX 设置为 currX = -increment; 只需将 * 更改为 +,就可以了。
  • @Justin 我怀疑任何想象中的加速如果它在做 IPC 是可以衡量的。重复添加也会导致更大的错误累积。
  • 两件事:在您的模拟中一致性有多重要?即,每次运行在相同的地方都有坑洼很重要,还是你能接受你的车有时会随机“停止”,即使它的轨迹上没有坑洼?此外,您体验延迟的频率如何?或者您是否试图过早地优化一些在完成后会运行得足够快的东西?
  • 我查看了上面的算法(不是您的代码)。为了实现这个“通常立即”的部分,我完全改变了算法(见下面我的解决方案)。

标签: java algorithm ipc


【解决方案1】:

在这里,使用 JMS 实现几乎即时的消息传递将是一项艰巨的任务。基本上,JMS 的设计更注重交付保证而不是交付速度。

Here 是一些可以帮助您加快 JMS 交付速度的点,但在 JMS 世界中,只能保证交付而不是速度。

我不得不提一下,您还应该考虑使用缓存解决方案。我有一个很好的answer 用于这种类型的缓存,以解决我关于 SO 的一个问题。 ....它摇滚!

【讨论】:

    【解决方案2】:

    我想说最大的改变是——

    去掉异步通信即JMS,加入一些同步通信机制。

    这可能是一个 RPC 调用/WebService 调用。

    ---更新---

    刚刚看到您的评论,您不能删除 JMS 部分作为更大系统的一部分。

    那么我们将不得不接受在 JMS 消息尚未到达之前我们无法做出决定。在这种情况下,可能几乎没有什么可以做的......

    【讨论】:

      【解决方案3】:

      我提议对您的上述算法进行更改,如下面的步骤所示。

      JMS 调用其他进程


      1a。从发送车辆的当前位置开始。

      1b。另一个进程将响应 JMS 消息,其中包含车辆位置可见区域中所有“坑洞位置”的列表。将此“可见坑洞位置”列表保留在客户端,以便在以下步骤中使用。

      1c。我们将可见区域定义为车辆的相邻区域,这样即使使用 JMS 调用其他进程(1 秒延迟 + 网络延迟),车辆的移动也不应该越过该区域。

      1d。每秒后,重复步骤 1a 和 1b,并替换客户端相对于您车辆当前位置的坑洞位置列表。

      .

      车辆运动观察者


      2a。实现一个可以接收车辆运动通知的观察者模式。

      2b。每次生成事件时,观察者都会检查车辆的位置是否与步骤 1b 中获取的可见坑洞列表中的条目之一匹配。

      2c。如果找到匹配,宾果!你必须停下车辆。

      .

      车辆行驶


      3a。注册 step-2a 观察者观察车辆的运动

      3b。等到您从步骤 1b 中获得至少第一个可见坑洞列表。

      3c。通过每 100 毫秒增加 X 值开始移动车辆。每次移动时,它都应该通知 step-2a 观察者。

      .

      下图说明:


      o - 地图上某处每个坑洞的实例 X - 移动车辆 . - 车辆后的路径 圆圈 - 车辆驾驶员的可见区域
      +---------------------------------------------+
      |                                             |
      |                    o                o       |
      |    o                                        |
      |                                             |
      |                                             |
      |                _.-''''`-._                  |
      |    o         ,'           `.             o  |
      |            ,'  o            `.              |
      |           .'    .            `.             |
      |           |      . .          |             |
      |           |         .         |   o         |
      |           |         X         |             |
      |   o       \                o  /             |
      |            \                 /              |
      |             `.             ,'               |
      |               `-._     _.-'                 |
      |                   `''''                     |
      |                                             |
      |                  o                          |
      |                                    o        |
      |                                             |
      |                                             |
      |     o                        o              |
      +---------------------------------------------+
      

      【讨论】:

        【解决方案4】:

        在我看来,碰撞检测空间的粒度太小,无法可靠地依赖 JMS。

        我会改变这一点,以便 Mover 接收到可以在本地使用的合理的地图块,例如如果整个地图为 100 x 100,则 Mover 应至少接收 10 x 10 的网格部分。

        可以在每次移动时在本地查询这部分网格是否有坑洼,当该位置接近 10 x 10 部分的边界时,可以请求下一个方格。

        这将为您提供一种双缓冲窗口,可以在其中加载新方块,同时继续根据旧方块评估剩余的移动。

        此外,您可能希望能够监听方格的变化,以便当有人在之前加载的方格中添加新坑洞时,将广播该新方格以及拥有该方格的所有客户端当前加载的可以重新加载。

        祝你好运。

        【讨论】:

          【解决方案5】:

          如你所说,

          我遇到的问题是“通常立即”部分。如果我没有足够快地得到响应,我认为它会影响我算法的整个时间。有什么更好的方法来处理这种情况?

          在理想世界中,您的计算机时钟是完美的,垃圾收集是原子的、瞬时的并且在 O(1) 内,网络没有延迟,操作系统没有中断,墨菲睡着了。

          由于您要处理的是真实世界的情况,因此您需要针对其典型的不确定性进行调整。首先,您需要统计数据。当然,Java GC 永远不能保证是实时的,但你可以有一个相当好的近似值,它可以在 90% 的时间内工作。剩下的 10% 可以由另一个“B 计划”处理,以此类推。

          换句话说:运行你的系统并尽可能地阻止它;收集使用统计数据;为这些情况制定最佳解决方法。例如,

          • 在模拟的模拟中插入随机延迟,看看它如何响应(单元测试!);也许您想每 500 毫秒运行一次 run() 方法?
          • 使用观察者模式(如其他地方建议的那样);
          • 在run()方法中运行尽可能少的代码;也许每1sec - epsilon 运行一次,其中 epsilon 是一个足够小的间隔,它解释了在足够大的样本中具有最高方差的延迟
          • 同时运行两个单独的线程,使用锁保持它们同步,平均它们的运行时间以获得更好的时钟

          在紧要关头,没有确切的解决方案,因为没有确切的“真实”世界。添加噪音,为更糟的情况做好准备,将其余部分平均化。

          【讨论】:

            【解决方案6】:

            好吧,如果这是一个模拟,那么你不会提前知道任何坑洼。 到目前为止,我最好的选择是搬家:

                            // do something
                            try {
                                messageQueue.poll(1000, TimeUnit.MILLISECONDS);
            
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }'
            

            在此之后或之前:

                        if (incrementor % (1000 / CAR_UPDATE_RATE_IN_MS) == 0) {
                        .. code ..
                        }
            

            并将 poll 中的参数从 1000 更改为 1(或 0,如果这意味着 poll 不会等待,而是立即退出)

            【讨论】:

            • +1 只需使用 messageQueue.poll() 如果没有消息等待则立即返回。
            【解决方案7】:

            可能使用观察者而不是消息传递来快速响应?

            【讨论】:

            • 需要 JMS...我是使用消息的更大系统的成员
            【解决方案8】:

            您在离散事件模拟方面有丰富的经验吗?这个概念是您提前在日历上安排事件,跟踪这些事件在列表中发生的时间,并在到达任何给定事件时使用一组规则更新系统状态。您不必担心运行调用的子例程所需的时间,而是在列表中有效地安排未来。这有意义吗?如果您需要更多信息/参考,请告诉我。

            【讨论】:

              【解决方案9】:

              难道不能使用并发或一些预读技术吗?

              我的意思是你的问题是在 messageQueue 上等待。如果你可以异步它,它不会有帮助吗?也许使用回调?

              您也许可以在调用进程 B 时保存状态并继续进程 A。如果进程 B 回复了一些错误,则停止并将状态恢复为保存的值。

              【讨论】:

                【解决方案10】:

                让计算机 B 在检测到坑洞的位置时将其发回,然后计算机 A 可以将车辆移动到该位置。如果计算机 A 上的车辆在撞到坑洼时所做的不仅仅是坐在那里,那么这篇文章可能会帮助您减少位置/方向/速度的突然变化: http://www.gamedev.net/reference/programming/features/cubicsplines/

                • 计算机 A:计算机 发送它的位置
                • 计算机 B: 检查坑洼的计算机

                【讨论】:

                  【解决方案11】:

                  除非您在提供实时保证的网络和操作系统上运行系统,否则偶尔会出现延迟。因此,您必须能够检测到这些延迟并决定如何响应——在汽车发现地图如何在其下方展开之前,您的模拟时间是否会停止?还是时间会继续流逝,但迟到通知的坑洼会出现在道路上比它本来应该有的更远的地方?还是迟到的坑洼被检测为迟到并被忽略?

                  我对 Java 消息传递的当前状态不太熟悉。您能否澄清messageQueue.poll 是否被阻止?如果您正在发送一条消息,然后阻止一个答案,它会引发一个问题,即为什么您不使用同步的东西,例如对远程对象的方法调用,因为这肯定会帮助基础设施毫不拖延地向您发送消息。

                  【讨论】:

                  • 投票被阻止...这些要求不是我的,而是给我的。
                  • 即使民意调查被阻塞是 Donal 的建议,您仍然可以继续处理
                  【解决方案12】:

                  我会节省 currX 计算的时间以及位置 (currX)

                  下次您计算 currX 时,您会查看自上次以来经过了多少毫秒 (System.currMillisec() - lastCalc),将其乘以速度并将其添加到 currX。然后将最后计算日期设置为现在。

                  编辑: - 小心你的单位(常量名称:MPS,注释:mph)

                  将其添加到声明中:

                  private static long compDate = System.currentTimeMillis();
                  private static long lastNotifDate = System.currentTimeMillis();
                  

                  以及run方法的开始:

                  currX += (System.currentTimeMillis() - compDate) * CONSTANT_SPEED_IN_MPS / 1000;
                  compDate = System.currentTimeMillis();
                  
                  if (compDate - lastNotifDate > 1000) {
                      lastNotifDate = System.currentTimeMillis();
                  ...
                  

                  【讨论】:

                    【解决方案13】:

                    也许您不需要代码实时运行,而只是模拟它并计算实时值?

                    【讨论】:

                    • 嗯...实际上,这确实需要实时运行..我只是“模拟”的一部分...我们的要求是:1秒的计算机时间是1秒的时钟时间。
                    • 如果需要实时,使用标准 JVM 是不可能的。由于 gc 以及操作系统可能不是实时的,JVM 上的时间是不可预测的。您需要在实时操作系统上运行像 java.sun.com/javase/technologies/realtime/index.jsp 这样的实时 JVM 来保证这一点。
                    • 只要您计算自上次模拟更新以来经过的时间量,并始终按该距离进行模拟,模拟将是实时的。如果你花一秒钟的时间进行垃圾收集(GCs 实际上非常快,所以不用担心),那么下一个时间步长会更大一些,并且会“赶上”实时
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-06-18
                    • 1970-01-01
                    • 2012-08-02
                    • 1970-01-01
                    • 2012-07-03
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多