更快地争夺百万数字

Faster Scramble Millions Numbers

我需要打乱巨大的整数数组(从 1,000,000 到 80,000,000 个元素)。

这些数组是按顺序创建的(第 1 步 1 到 n),我需要确保没有一个序号丢失 - 因此我无法创建 "random numbers" 个数组。

因此,我使用单个 FOR-NEXT 创建它们(花费的时间非常短,大​​约 2 毫秒),并且我使用 Fisher-Yates-Durnstenfeld 随机播放算法,如下所示。

首先我创建数组:

 dim Huge(0 to 80,000,000) as Integer
 For x as integer = 1 to 79,999,999
     Huge(x) = x
 Next

创建真的只需要几毫秒...

之后,我打乱数字:

    Shuffle(Huge)

其中 Shuffle 是 在 Parallel/Multitasking 环境 中调用的例程。因此,我需要锁定 Fisher-Yates-Durstenfeld 例程中使用的 RANDOM 函数(以在交换值和位置时提供良好的熵):

Fisher-Yates 洗牌

Friend Sub Shuffle(ByRef Buffer As Integer())
  For max As Integer = Buffer.Length - 1 To 1 Step -1
      ExtractRandomItem(Buffer, max)
  Next
End Sub

Friend Sub ExtractRandomItem(ByRef buffer As Integer(), max As Integer)
  Dim random As Integer = GetRandomNumber(max + 1)
  If random = max Then
     random -= 1
  End If

  Dim temp As Integer = buffer(max)
  buffer(max) = buffer(random)
  buffer(random) = temp
End Sub

Friend Function GetRandomNumber(max As Integer) As Integer
  Dim _random As New System.Random
  SyncLock _random
      Return _random.Next(1,max)
  End SyncLock
End Function

我的问题

这个洗牌例程非常适合随机打乱数组,但是,如果打乱 8192 个元素只需要 2 毫秒,那么 24,000,000 个元素需要 32 秒 - 太多时间了为了我的需要。

你知道有什么方法可以增强这个过程并让它更快吗?

或者你知道另一种非确定性洗牌算法比我的算法运行得更快吗?

注意:我已经尝试过PARALLEL.FOR,但无论如何我都没有表达能力(实际上只有几毫秒)

而且我真的不需要 "random-like" 洗牌,只需要一个不确定的排序。

感谢您的帮助

更新

由于Jdweng和Hans的相关评论,感觉预约很有意思:

更新二

有些人推荐了好的做法,但他们没有资格解决问题。让我逐一讨论:

你表现不佳的主要原因是你为每个随机数创建了一个新的随机数生成器。我只创建一个就获得了 7 倍的速度提升。我还将所有代码合并到一个函数中,以最大限度地优化 .net。最终结果快了不到 10 倍(在我的 32 位 Win7 虚拟机上,对于 80,000,000 个项目,117 秒到 12 秒)。

Friend Sub Shuffle(ByRef Buffer As Integer())
    Dim _random As New System.Random
    For max As Integer = Buffer.Length - 1 To 1 Step -1
        Dim random As Integer = _random.Next(1, max)
        Dim temp As Integer = Buffer(max)
        Buffer(max) = Buffer(random)
        Buffer(random) = temp
    Next
End Sub

请注意,随机数生成器不需要锁,因为对 Shuffle 的每次调用都有自己的副本,并且不会跨线程共享。

70% 的时间花在了从 Buffer 中读取值(随机)上。这是因为它可能不在 CPU 缓存中并且必须访问主内存,而读取 Buffer(max) 仅为 .5%,因为它是按顺序降序读取的,并且 CPU 可以将其预读入缓存。 20% 用于计算随机数,因此使用更快的生成器,您可以节省更多时间但不会太多。

最大的胜利将是一种算法,它不会为每个元素随机访问数组。我听从了 Hans Passant 在问题评论中的建议并用谷歌搜索 MergeShuffle。它似乎就是这样一种算法(多次顺序访问而不是随机单次访问)。而且除了允许分区外,它不需要更多的内存。

我制作了它的原型,我看到 8000 万个项目在 250 万个项目切换到 Fisher-Yates 时速度提高了 20%。