【问题标题】:How to monitor/show progress during a C++ sort如何在 C++ 排序期间监视/显示进度
【发布时间】:2010-06-22 16:17:49
【问题描述】:

我打算编写一个交互式 C++ 几何处理插件,该插件将经常对大量数据进行排序。虽然初步迹象表明排序只需要一两秒钟,但我更愿意在这段时间内显示进度 - 即我想每秒更新几次进度指示器。这比打开等待光标并让用户看到的程序冻结一段不确定的时间(即使只是几秒钟)更可取。

如果我要使用 std::sort 之类的东西,我可以使用比较功能不时更新进度指示器,但我不知道“完成百分比”。我还可以将排序分解为子排序,更新子排序之间的进度,然后合并。我最好的选择可能是编写自己的排序方法,尽管我不知道要获得与 std::sort 一样好的性能(并确保正确性)需要付出多少努力。无论如何,该排序方法偶尔会向回调方法发送“完成百分比”。

我想知道其他人是否遇到并解决了这个问题 - 我希望标准库中可能有一种排序方法可以满足我的需求,或者其他一些我没有想到的技术。

更新:感谢您迄今为止的精彩回答。有一些非常好的建议,我将推迟选择接受的答案,直到我有机会在我即将进行的项目中测试这些想法。

更新 2: 我完成了我的项目,结果证明这不是问题(至少对客户而言。由于他们将销售该软件,他们可能仍会从他们的会改变主意的客户)。选择一个被接受的答案很困难,因为有很多好的答案,但最后我选择的那个指向了一篇关于 Merge Sort 的 wiki 文章,它有一个非常令人回味的动画。如果我需要继续这样做,那是我会采取的第一个策略)。

【问题讨论】:

  • 我个人会推迟添加这样的功能,直到观察到排序的实际性能。否则,它正在解决一个可能不存在的问题。您也可以走简单的路线并在某种日志文本控件或状态栏中显示“排序...”。
  • @Reinderien:同意,如果没坏就不要修。但我试图提前考虑这一点。而我在 3D 图形和几何处理方面的经验是,用户很容易用比你想象的更大的模型和数据扼杀任何东西。

标签: c++ algorithm sorting progress-bar


【解决方案1】:

我认为,即使您编写了自己的排序,如果您希望进度指示器准确无误,也必须进行大量仔细测量。如果您只想要一个近似的进度指示器,那么您可以使用一些指标,例如“比较元素之间的平均距离”或“与快速排序的平均预期数量相比的比较次数”作为您的指标,并实施您已经提到的比较想法。

是的,我假设您不是一个完全的白痴,并且不打算在每次比较时更新进度指示器。如果你这样做了,你会花费更多的时间来显示进度而不是排序。

例如,您通常会期望快速排序的 n log2 n 操作。对涉及多少比较的分析比一般测量更详细,也更准确,但出于本示例的目的,我们只是假设。因此,您可以计算比较并报告number_of_comparisons / (n log2 n) 作为您对进度的估计。

由于这只是一个平均指标,我会进行一些实验,看看您的估计偏离了多远,并加入一些虚假因素以使其与平均预期情况一致。你也可以有一个进度条,通过“这就是我认为我会完成的地方”来指示不确定性。指示器和指示器后面的一些空格。

即使您使用自己的排序并想出一个看起来更精确的度量,进度条仍然不会顺利更新,效果也差不多。您确定排序需要多长时间的唯一方法是使用稍微慢一些但真正可预测的排序,在这种情况下,您可以根据元素数量预测需要多长时间,或者使用非常快的排序在特定情况下行为难以预测的排序,在这种情况下,没有真正的方法可以获得完全准确的进度条。

子任务的可预测性和比较总数的可预测性密切相关。所以我真的不认为子任务比比较总数更好。

如果您想使用自己的排序并且可预测性是您的最高目标,请选择heapsort。它仍然是O(n log2 n) 排序,并且接近于最小比较排序(或者我记得从阅读 Knuth 时)。无论提供何种数据集,它也需要非常可预测的时间才能完成。这是较慢的O(n log2 n) 排序之一,但仍然如此。

正如您的一位评论者所提到的,您可能正在解决一个实际上并不存在的问题。先做一些实验。这个问题是一个有趣的智力挑战,尽管它有什么用处。 :-)

【讨论】:

  • +1 表示提前考虑如何衡量进度。如果我要自己写,我仍然必须弄清楚这一点。我想真正的问题是我知道算法的内部状态有多大优势,而不仅仅是迄今为止的比较次数。感谢您假设我不是一个关于每次比较都更新进度指示器的完整白痴,尽管您可以放心地假设我是一个关于排序的完整白痴。
  • @brainjam:我不是算法专家,但据我所知,了解内部状态并不会像您想象的那样为您提供有用的数据。例如,在列表被分成两半后,快速排序可能会在一侧花费非常少的时间,而在另一侧花费非常长的时间。如果您选择可预测的排序方式,您就可以像预测完成各种子任务所需的时间一样轻松地预测比较行为的数量。
  • 进度指示器的准确性并不像让用户在时间流逝时保持娱乐、设定他们的期望并允许他们取消那么重要。所以我想我会把估计值加倍到2*n*log2(n),如果排序完成的速度比预期的快,那就更好了。
  • @brainjam:这个建议怎么样——在每次排序结束时记录实际和估计的比较次数。这样,您在运行程序时会随着时间的推移保留一些统计数据。最终您可以退出登录,但您的统计数据应该可以帮助您稍微提高准确性。
【解决方案2】:

由于 std::sort 是基于模板的,源代码应该在标题中可用。您可以复制它并插入您的进度回调。最大的问题是预测您离完成有多近 - 大多数排序功能将基于快速排序,它并不总是进行相同数量的比较。

编写自己的Merge sort 是可能的;算法简单,步数明确。

【讨论】:

【解决方案3】:

我会推荐您的第二个选项:使用std::sort 或其他标准排序功能,如qsort,并让比较器报告其进度。但不要在每次比较时都更新——那会难以忍受缓慢——而是每(比如)100 毫秒更新一次。

【讨论】:

  • 但这并不能回答 OP 的大问题。您如何实际确定排序使用此方法完成了多长时间?
  • 我想如果你给比较器在其构造函数中数组的大小,然后使用上面的 Omifarious 近似值(将有大约 (n lg n) 个比较)。然后比较器可以跟踪它被调用了多少次。我不确定,也没有完全考虑清楚,但我认为合并排序可能适合跟踪进度。但是,归并排序当然不是内推。仍然合并排序是 (n lg n) 并且可能是可以接受的。
  • @Craig W. Wright:这会很困难,因为 STL 比较函子不允许有状态。
  • @Billy:怎么会这样?比较函数需要产生一致的(时间不变的)结果,但是 AFAICT 没有禁止不改变返回值的副作用。
  • @Ben:它本身并没有被禁止,但是算法允许按值传递函子,这会给你留下一堆副本,每个副本都有单独的计数。您必须将信息存储在仿函数之外以获得一致的结果(我猜您可以让仿函数存储一个指针......)
【解决方案4】:

我认为您的问题如下:

  1. 您希望在单个连续过程中触发离散事件。
  2. 这个细分只是为了告诉用户事情正在进行中。

我的建议是:

  1. 使用类似 http://ajaxload.info/ 的加载图标,或者如果它不是基于 gui 的环境,只需拼写加载。由于事件不到 2 秒,因此这不是问题。如果等待时间超过 10 秒,预计会挂断。

  2. 编写自己的排序方法确实会带来很多线程安全问题,如果您的代码使用多线程或将来一定会这样做,这可能会导致问题。

    李>

3.另一个重要信息,您应该考虑每次要排序时数据的乱序程度,因此实际上您将衡量存在的随机性程度,以及您可能需要的预期计算次数去做。您可以将此信息用作需要多少交换的指标,这反过来又可以在您遍历排序时计算。玩弄数据。

【讨论】:

    【解决方案5】:

    使用蛮力:)

    int elem_num = raw_data.size();
    int percentage_delta = 100/(elem_num/20);
    int percentage = 0;
    int i = 0;
    std::multiset<Elem*> sorted_data(&compareElemFunc);
    foreach(Elem& elem, raw_data)
    {
        sorted_data.insert(&elem);
        if(i%20)
        {
            updateProgressBar(percentage);
            percentage += percentage_delta;
        }
        i++;
    }
    //now, your data is perfectly sorted, iterate through sorted_data
    

    (如果您不想实现自己的 std::sort() 并且因为我缺乏完整的要求)

    【讨论】:

    • 我想这是 O(n logn),但我想知道它与执行 std::sort 相比如何。如果 std::sort 需要 1 秒,而这个解决方案需要 10 秒,我会三思而后行。这个解决方案的好处是您可以随时取消该过程。顺便说一句,我会将进度更新因子从 20 更改为 1000 甚至 10000——每秒更新几次就足够了。
    【解决方案6】:

    当每个部分完成时,使用observer pattern 向父级发回信号。使用它和需要排序的元素总数,您可以实时更新进度条。

    【讨论】:

      【解决方案7】:

      我不建议尝试破解 std::sort。这通常通过 introsort 实现,并且是一个非常快速的 NLogN 操作。构建要排序的容器通常比排序数据更昂贵。

      但是,如果您要实现进度条,我建议您将排序放在单独的线程中。通常,多线程应用程序比单线程应用程序更难编写和维护,但您可以采用一种不适用于这种进度条情况的方式来实现。您的应用程序仍然可以主要是单线程的,没有任何并发​​操作被执行,除了这个进度条和可能的一些事件处理来保持 UI 响应。当您准备好对数据进行排序时,只需启动另一个线程来执行此操作并将主线程置于等待循环中,直到排序线程完成,在这里和那里休眠并同时升级进度条。

      您可以将这种非侵入式方法推广到任何类型的耗时操作,而无需在整个代码中散布 update_progress_bar() 类型调用或深入研究 std::sort 的实现或尝试重新发明轮子。因为主线程将处于等待/更新进度条状态,因此在某种意义上阻塞,直到您的工作线程完成,您没有任何与多线程相关的问题(需要线程同步以访问整个您的共享资源应用程序,进度计数器、竞争条件、死锁等除外)。它也是您可以实现的最平滑的进度计数器,因为它会同时更新。

      如果您担心与锁定进度计数器相关的效率,只需使用原子操作来增加它。

      至于确定排序算法的进展程度,有几种方法可以做到。一种是让它以您拥有的数据大小运行一次,并尝试预测后续运行所需的时间。这完全是非侵入性的,但有点难以做到,但是,如果做得好,它将比定期增加计数器更准确地监控进度(这忽略了间隔可能不会花费均匀时间的事实)。第二种更简单但有点邪恶的方法是修改比较器谓词以增加进度计数器。使用 state 进行谓词通常是不受欢迎的,但它比仅仅因为你想要一个进度计数器而尝试实现自己的 introsort 更邪恶。

      另外,如果您的 introsort 需要这么长时间,我不得不怀疑,您的容器是否存储了这些三角形对象或指向它们的指针?如果是前者,您可能需要考虑后者,因为它会显着加快速度。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-21
        • 2019-01-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多