【问题标题】:Manipulating very large binary strings in c#在 C# 中操作非常大的二进制字符串
【发布时间】:2011-04-01 00:00:21
【问题描述】:

我正在做一个遗传算法项目,我将我的染色体编码成一个二进制字符串,其中每 32 位代表一个值。问题是我正在优化的函数有超过 3000 个参数,这意味着我的位串中有超过 96000 位,我对此进行的操作只是为了减慢......

我进行了如下操作:我有一个二进制类,我正在创建一个布尔数组,它代表我的大二进制字符串。然后我用各种移位和移动等操作这个二进制字符串。

我的问题是,有没有更好的方法来做到这一点?速度简直要命。我敢肯定,如果我只对一个位字符串执行此操作会很好,但我必须对 25 位字符串进行超过 1000 代的操作。

【问题讨论】:

  • 像这样以速度为关键的粗大、密集的操作可能会受益于非托管(本机)方法 - 例如一个 C++ 库。但是,对于这个问题,看到一些示例代码/数据/操作会很有好处。
  • 二进制字符串是什么意思?您使用的是字节 [] 吗?还是一个 int[] 或一些类似的结构?如果是这样,这些操作,即使是 96k 位,也应该非常快。如果您实际上使用的是文本字符串,那显然是性能问题的根源。
  • 我没有使用文本字符串,我使用的是 bool[] 数组。所以它确实应该很快。给出一个操作样本假设我有以下布尔数组:{a,b,c,d,e,f,g,h,i} (其中所有字母都是真或假)然后一个操作可能会做在布尔值 {x,y,z} 的 b 之后插入,这样我的数组现在看起来像 {a,b,x,y,z,c,d,e,f}。这需要很长的数组......
  • @Erik:您是否考虑过使用 SQL Server 做类似的事情?这是您正在执行的大量基于集合的操作,SQL Server 可能比您编写的任何程序代码都快很多
  • @casperOne:我的程序和 SQL 服务器之间的通信不会造成巨大的瓶颈吗?

标签: c# string binary


【解决方案1】:

我要做的就是退后一步。您的分析似乎与实现细节密切相关,即您选择了 bool[] 作为表示位串的方式。

清除您对 bool 和数组的想法,并列出您实际需要执行的操作、它们发生的频率以及它们必须有多快的完整列表。理想情况下,请考虑您的速度要求是平均速度还是最坏情况下的速度。 (有许多数据结构通过对每千次廉价操作进行一次昂贵的操作来获得较高的平均速度;如果有任何昂贵的操作是不可接受的,那么您需要预先知道这一点。)

获得该列表后,您就可以研究哪些数据结构运行良好。

例如,假设您的操作列表是:

  • 按照 32 位的顺序构建位序列
  • 将大约 3000 个位序列连接在一起以形成新的位序列
  • 将新的位序列快速插入到特定位置的现有长位序列中

仅考虑该操作列表,我认为您想要的数据结构是可连接双端队列。可连接双端队列支持在任一端快速插入,并且可以有效地分解为两个双端队列。然后很容易将东西插入双端队列的中间:分解双端队列,将项目插入一半的末尾,然后再次加入它们。

但是,如果您随后对问题添加另一个操作,例如“在 90000 位序列中的任意位置搜索特定位串,并在亚线性时间内找到结果”,那么只需一个可连接双端队列不会这样做。搜索双端队列很慢。还有其他支持该操作的数据结构。

【讨论】:

  • 这很有趣,我不必搜索我的序列,事实上,我的所有操作都可以通过在结束/开始组合处的一些智能拆分/插入来执行......我会明确阅读可连接双端队列结构!
  • @Erik:我的博客上有一个简单的不可变双端队列实现,你可以使用;没有明示或暗示的保证,我没有测试过。它没有实现“中间分割”功能;留作练习。 :-) blogs.msdn.com/b/ericlippert/archive/2008/02/12/… 该系列文章基于一篇关于手指树的论文:soi.city.ac.uk/~ross/papers/FingerTree.pdf 详细了解如何构造一个可以有效拆分并重新组合的双端队列。
  • 原谅我的无知;是由双向链表简单实现的可连接双端队列,还是其中有我不知道的技巧?
  • @configurator: 双向链表使一个优秀的 mutable 可链双端队列成为一个糟糕的 immutable 可链双端队列,因为没有办法构建持久性的数据结构。也就是说,您不能像使用单链表那样重复使用不可变双向链表的一部分来创建另一个。
【解决方案2】:

如果我理解正确,您将位数组编码为bool[]。第一个明显的优化是将其更改为int[](甚至long[]),并尽可能利用整个机器字上的位操作。

例如,这将使轮班效率提高约 4 倍。

【讨论】:

  • 这是可能的,但我有一些操作,我必须在特定索引处插入 k 位,这会导致索引右侧的所有位移动 k(向右)。我认为我不能在 int[] 或 long[] 上做这样的事情,因为我只能分别在每个 32 位或 64 位上拆分。然后必须移动 32 位或 64 位...有什么想法吗?
  • @Erik 当然可以。您只需为整个位数组手动实现任意数量的位移。你应该这样做,它一定是最有效的。
  • 假设我有一个 int 623 = 1001101111。现在我如何在不先将 623 转换回 bool[] 数组或位表示的情况下从 100[1101]111 中提取 1101?
  • @Erik 所以结果应该是100[]111[] 是提取值的地方?那么,对于普通的位向量(= 整数)你会怎么做呢?您通过位掩码和按位屏蔽其余两个部分(100 和 111),然后复制高位(100),将它们向右移动三个位置,然后通过按位将它们粘贴到低位(111)或者。现在您需要为您的 BitVector 结构实现按位或、按位与和位移。
  • 由于我不太熟悉这些操作与我想做的操作的性能差异,我不得不问,这样的设置在性能方面与我现在在做什么(只需将每个布尔值上移 4)。好像加了很多操作,会不会更快?
【解决方案3】:

BitArray 类没有帮助吗?

【讨论】:

  • 我相信 BitArray 正是我实现的,一个代表二进制字符串的 bool[] 数组。
  • BitArray 的行为类似于 bool[] 数组 - 它不是 bool 数组
【解决方案4】:

BitArray 可能会比布尔数组快,但您仍然无法获得移动 96000 位的内置支持。

GPU 非常擅长大规模位运算。也许BrahmaCUDA.NETAccelerator 可以提供服务?

【讨论】:

  • 嗯,确实,如果我有使用 GPU 进行计算的经验……也许值得花时间学习?
  • 可能; GPU 正被用于除图形处理之外的许多“令人尴尬的并行”算法,例如加密破解。如果我们谈论很多非常简单的操作,那就完美了。
  • 我会明确地阅读这个,如果我幸运的话,它可能是我需要的。谢谢!
  • 不幸的是,GPU 在“廉价”操作方面非常糟糕,因为复制大量数据的成本相对较高,而且只能通过 GPU 可以非常快速地执行并行计算的事实来分摊。但是位操作无论如何都很快,所以这种方式并没有太大的收获,而且一旦你开始非本地操作(例如删除位,见上文)SIMD 需要相当复杂的操作。
  • @Konrad 很有可能你是对的,但通过模糊的问题描述很难知道。
【解决方案5】:

让我明白;目前,您正在为“染色体”使用一系列 32 位值。我们是在谈论 DNA 染色体还是神经进化算法染色体?

如果是 DNA,你处理 4 个值; A,C,G,T。这可以用 2 位编码,使一个字节能够保存 4 个值。你的 3000 个元素的染色体序列可以存储在一个 750 个元素的字节数组中;这没什么,真的。

您的两个最昂贵的操作是往返压缩比特流。我会推荐一个字节键枚举:

public enum DnaMarker : byte { A, C, G, T };

然后,您通过一次操作从其中的 4 个变为一个字节:

public static byte ToByteCode(this DnaMarker[] markers)
{
   byte output = 0;
   for(byte i=0;i<4;i++)
      output = (output << 2) + (byte)markers[i];
}

...并用类似这样的方式将它们解析出来:

public static DnaMarker[] ToMarkers(this byte input)
{
   var result = new byte[4];
   for(byte i=0;i<4;i++)
       result[i] = (DnaMarker)(input - (input >> (2*(i+1))));

   return result;
}

与在堆中分配和使用数组相比,使用四个参数(必要时输出)可能会略微提高性能。但是,你失去了使代码更紧凑的迭代。

现在,因为您将它们打包成四字节“块”,如果您的序列长度并不总是四的精确倍数,您最终会用零值“填充”块的末尾(A )。解决这个问题很麻烦,但如果你有一个 32 位整数来告诉你标记的确切数量,你可以简单地丢弃你在字节流中找到的任何东西。

从这里开始,无限可能;您可以通过简单地对每个数组调用 ToString() 将枚举数组转换为字符串,同样您可以输入一个字符串并通过使用 Enum.Parse() 迭代来获取一个枚举数组。

请始终记住,除非内存非常宝贵(通常不是这样),否则以易于使用的格式而不是最紧凑的格式处理数据几乎总是更快。一个大的例外是网络传输。如果您必须通过 Internet 发送 750 字节而不是 12KB,则较小的大小具有明显的优势。

【讨论】:

  • 当然这是可能的,它只是从 base-2 表示系统迁移到 base-4 系统的一个问题。我宁愿坚持使用 base-2 系统,因为它允许对遗传密码进行更深入的更改,而且所有计算机都是 base-2 系统。
  • 我认为你误解了这个问题。 “遗传算法”只是一个比喻(虽然非常贴切)。通常没有令人信服的理由来使用 DNA 碱基对问题进行建模。重点不在于对生物 DNA 进行建模。相反,这是建模随机突变、重组和非随机选择的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-02-22
  • 1970-01-01
  • 2012-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-09
相关资源
最近更新 更多