【问题标题】:Better datastructure for a specific type of query为特定类型的查询提供更好的数据结构
【发布时间】:2011-06-20 17:44:27
【问题描述】:

很久以前就解决了这个问题,但这个问题最近引起了一些关注,所以我添加了我的解决方案(以及一些澄清)。见下文。


我有一个 32 位整数列表,我想找到其中((item ^ bits) & mask) == 0 为真的子集。问题是,bitsmask 都可以取任何值(mask 偏向于没有设置很多位),所以我不能简单地预先计算所有可能的组合。

列表不是很大,通常大约 500 项,所以理论上看起来不错的东西(例如二叉树,其中掩码中的每个位都可以跳过整个子树)实际上很慢。尽管跳过了大量的测试,即使只有两层的树也比简单的方法慢一点。

目前我遍历整个列表并测试每个项目。它曾经很快,但它发生了数百万次,每次都使用不同的bitsmask,因此缓存结果无济于事。这部分程序占用了它使用的总 CPU 时间的 40% 以上。

foreach (var row in validRows.Keys)
{
    // this single line here takes 40% of the total program time, according to ANTS 5
    if (((oldrow ^ row) & oldused) == 0)
    // the other magic takes no significant time, according to ANTS
    {
        if (y > 1 && ((((row ^ prev) | yminone) + 1) >> rows.Length) == 0)
        {
            continue;
        }
        if (dict.ContainsKey(row))
            continue;

        dict.Add(row, true);

        rows[y] = row;
        count(y + 1, dict);   // this is a recursive call.

        dict.Remove(row);
    }
}

我收集了一些统计数据。事实证明,在 179000 个查询中,超过 130000 个仅返回 1 个项目。对我来说,这听起来像是进行某种优化的机会,但我不确定如何或什么。


对于这个特定的子问题,预处理有很大帮助。我现在为输入中的每一行创建一个可能性数组,它只是由(int row) => ((inputrow ^ row) & inputfilledmask) == 0 过滤的validRows(现在是一个数组而不是字典)。

实际的问题是,给定一个部分填充的 8x8 布尔矩阵,计算所有满足以下规则的赋值:

  1. 每行 4 个 1 和 4 个 0
  2. 每列 4 个 1 和 4 个 0
  3. 垂直或水平相邻的不超过 2 个
  4. 垂直或水平相邻的零不超过 2 个
  5. 两行不能相等
  6. 两列不能相等

我现在是这样解决的:

对于每一行,将 34 个有效行的列表过滤到可以在其顶部分配的行(即,与标记填充单元格的掩码中的位相对应的所有位在输入行中是相等的以及可以在其上分配的行)。

然后以递归方式,在该点用所有可能的行填充下一行。这意味着它必须在过滤列表中,它必须还没有被使用过(即不在散列集中),并且进行了测试以确保它不违反第 4 条规则中的第 3 条。为了修剪递归树的更多子树,我还使用了两个额外的整数,一个用于跟踪每列中零的数量(每列一个半字节),一个用于跟踪零。简明测试会忽略可能违反第二条规则的行。这些整数根据输入进行初始化(加上0x33333333,因此一列中的 4 个不设置半字节的最高位,但有 5 个或更多),并且仅使用之前为空的单元格进行更新。

最后,在递归树的底部,最后一行完全由计算列中的 1 和 0 的两个整数决定(即使只有一个也足以确定最后一行)。然后对重复的列进行测试——这是唯一不能通过构造自动保证的事情。总之,时间从大约一分钟减少到大约十分之一秒(通常更少)。

我仍然愿意接受建议(尽管这会使这个问题成为一个完全不同的问题,真的)。此计数例程用于通过蛮力生成“良好”的初始配置 - 它运行得越快,在分配的时间内结果就越好。

【问题讨论】:

  • 您对这些价值观了解多少?例如,低位可能是均匀分布的吗? mask 的可能值是多少?
  • 在不了解数据的任何特征或计算目的的情况下,很难提出任何优化建议。例如,您能否指定是否要屏蔽单个位、几个位或大部分位?计算是为了什么?一般结果是只包括少数数字还是大部分数字?处理结果(例如创建一个 List 对象并向其添加数字)可能比进行计算需要更多的工作,那么您希望结果如何?
  • bitsmask 也是 32 位的吗?
  • @Jon Skeet:mask 可能设置了大约 0 到 5 位(虽然可能更多,但不太可能),这些值的连续位永远不会超过 2 个在低位(即从不“000”或“1111”),但高位(比如最高16位)可以全为零。
  • bitsmask 也是 32 位的。结果不必在列表中,我只是遍历它们并一个一个地使用它们。平均而言,查询返回 17% 的项目。

标签: c# data-structures


【解决方案1】:

我认为您专注于解决方案的错误部分。虽然很多 CPU 时间都在这个循环中,但它本身并不能进行太多优化。

我使用预先计算的列表进行了测试,其中包含为特定位设置或清除位的整数,并根据位和掩码中的值组合列表。虽然速度相当快,但它的开销仍然足以使其花费比仅计算值多十倍的时间。

您必须在循环之外查看数据的实际含义,才能找到消除某些工作的方法。

【讨论】:

  • 已添加代码。根据 ANTS 5,值的过滤是最耗时的操作。
【解决方案2】:

您要求更好的数据结构,但如果没有呢?

您可以考虑退后一步,查看您的问题,看看您是否可以使用多个线程或并行结构,以便一次使用多个处理器。

【讨论】:

    猜你喜欢
    • 2021-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-06
    相关资源
    最近更新 更多