有没有更有效的方法在 C# 中生成 4000 万个字母数字唯一随机字符串

Is there a more efficient way to generate 40 million alphanumeric unique random strings in C#

我创建了一个基于 windows 表单的小型应用程序来生成长度为 8 的随机唯一字母数字字符串。应用程序在少量情况下运行良好,但当我尝试生成 4000 万(根据我的要求)字符串时,它永远停滞不前。请帮助我提高效率,以便快速生成字符串。

以下是我使用的完整代码。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;


namespace RandomeString
{
    public partial class Form1 : Form
    {
        private const string Letters = "abcdefghijklmnpqrstuvwxyz";
        private readonly char[] alphanumeric = (Letters + Letters.ToLower() + "abcdefghijklmnpqrstuvwxyz0123456789").ToCharArray();
        private static Random random = new Random();

        private int _ticks;

        public Form1()
        {
            InitializeComponent();

        }

        private void button1_Click(object sender, EventArgs e)
        {

            if (string.IsNullOrEmpty(textBox1.Text) || string.IsNullOrWhiteSpace(textBox2.Text))
            {
                string message = "Please provide required length and numbers of strings count.";
                string title = "Input Missing";
                MessageBoxButtons buttons = MessageBoxButtons.OK;
                DialogResult result = MessageBox.Show(message, title, buttons, MessageBoxIcon.Warning);
            }
            else
            {
                int ValuesCount;
                ValuesCount = Convert.ToInt32(textBox2.Text);

                for (int i = 1; i <= ValuesCount; i++)
                {
                    listBox1.Items.Add(RandomString(Convert.ToInt32(textBox1.Text)));
                }
            }
        }

        public static string RandomString(int length)
        {
            const string chars = "abcdefghijklmnpqrstuvwxyz0123456789";
            return new string(Enumerable.Repeat(chars, length)
              .Select(s => s[random.Next(s.Length)]).ToArray());
        }

        private void button2_Click(object sender, EventArgs e)
        {
            try
            {
                StringBuilder sb = new StringBuilder();
                foreach (object row in listBox1.Items)
                {
                    sb.Append(row.ToString());
                    sb.AppendLine();
                }
                sb.Remove(sb.Length - 1, 1); // Just to avoid copying last empty row
                Clipboard.SetData(System.Windows.Forms.DataFormats.Text, sb.ToString());
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            _ticks++;
            this.Text = _ticks.ToString();
        }
    }
}

加快速度的一种方法是避免使用 LINQ。例如,看看这两个实现:

public static string LinqStuff(int length)
{
    const string chars = "abcdefghijklmnpqrstuvwxyz0123456789";
    return new string(Enumerable.Repeat(chars, length)
        .Select(s => s[random.Next(s.Length)]).ToArray());
}

public static string ManualStuff(int length)
{
    const string chars = "abcdefghijklmnpqrstuvwxyz0123456789";
    const int clength = 35;

    var buffer = new char[length];
    for(var i = 0; i < length; ++i)
    {
        buffer[i] = chars[random.Next(clength)];
    }

    return new string(buffer);
}

运行 通过这个:

private void TestThis(long iterations)
{
    Console.WriteLine($"Running {iterations} iterations...");

    var sw = new Stopwatch();
    sw.Start();
    for (long i = 0; i < iterations; ++i)
    {
        LinqStuff(20);
    }
    sw.Stop();
    Console.WriteLine($"LINQ took {sw.ElapsedMilliseconds} ms.");

    sw.Reset();
    sw.Start();
    for (long i = 0; i < iterations; ++i)
    {
        ManualStuff(20);
    }
    sw.Stop();
    Console.WriteLine($"Manual took {sw.ElapsedMilliseconds} ms.");
}

有了这个:

TestThis(50_000_000);

产生了这些结果:

LINQ took 28272 ms.
Manual took 9449 ms.

因此,通过使用 LINQ,生成字符串所需的时间增加了 3 倍。

您可以对此进行更多调整并挤出几秒钟,可能(例如,向每个调用发送相同的 char[] 缓冲区)

  1. 不要使用 linq
  2. pre-allocate记忆
  3. 不要将其放入 UI 控件中
  4. 尽可能多地使用内核和线程。
  5. 使用直接内存。
  6. 将结果写入文件,而不是使用剪贴板

这可能会更快、更有效地完成,请参阅注释。但是,我可以在 200 毫秒内生成字符

注意 : Span<T> 会给出更好的结果,但是由于 lamdas,它更容易受到小打击来自 fixed 并使用指针

private const string Chars = "abcdefghijklmnpqrstuvwxyz0123456789";

private static readonly ThreadLocal<Random> _random =
   new ThreadLocal<Random>(() => new Random());

public static unsafe void Do(byte[] array, int index)
{
   var r = _random.Value;
   fixed (byte* pArray = array)
   {
      var pLen = pArray + ((index + 1) * 1000000);
      int i = 1;
      for (var p = pArray + (index * 1000000); p < pLen; p++ ,i++)
         if ((i % 9) == 0) *p = (byte)'\r';
         else if ((i % 10) == 0) *p = (byte)'\n';
         else *p = (byte)Chars[r.Next(35)];
   }
}

public static async Task Main(string[] args)
{
   var array = new byte[40000000 * ( 8 + 2)];

   var sw = Stopwatch.StartNew();
   Parallel.For(0, 39, (index) => Do(array, index));

   Console.WriteLine(sw.Elapsed);

   sw = Stopwatch.StartNew();
   await using (var fs = new FileStream(@"D:\asdasd.txt", FileMode.Create,FileAccess.Write,FileShare.None, 1024*1024,FileOptions.Asynchronous|FileOptions.SequentialScan))
      await fs.WriteAsync(array,0, array.Length);
   Console.WriteLine(sw.Elapsed);
}

输出

00:00:00.1768141
00:00:00.4369418
  • 注 1 :显然,除了原始生成之外,我还没有真正考虑过这个问题还有其他的考虑。

  • 注 2 :此外,这将在大对象堆上结束,所以买家要小心。您需要将它们直接生成到文件中,以免最终出现在 LOB

  • 注 3 :我不保证随机分布,可能会有不同的随机数生成器整体更好

  • 注 4 : 我使用了 40 个索引,因为数学很简单,如果你可以将你的线程匹配到 cores