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

问题描述

我创建了一个基于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. 预分配内存
  3. 请勿将其放入UI控件
  4. 使用尽可能多的内核和线程。
  5. 使用直接内存。
  6. 将结果写入文件,而不是使用剪贴板

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

注意Span<T>会产生更好的结果,但是由于存在lamda,因此更容易从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,array.Length);
   Console.WriteLine(sw.Elapsed);
}

输出

00:00:00.1768141
00:00:00.4369418
  • 注1 :除了原始代之外,我还没有真正考虑这一点,显然还有其他考虑。

  • 注意2 :这也将最终出现在大对象堆上,因此买家要当心。您将需要直接生成它们以归档,因此可以避免将其保存在LOB中

  • 注3 :我对随机分布不做任何保证,可能总体上使用不同的随机数生成器会更好

  • 注4 :我使用40个索引,因为数学很简单,如果可以将线程与内核匹配,则结果会好一点