有没有更有效的方法在 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[]
缓冲区)
- 不要使用 linq
- pre-allocate记忆
- 不要将其放入 UI 控件中
- 尽可能多地使用内核和线程。
- 使用直接内存。
- 将结果写入文件,而不是使用剪贴板
这可能会更快、更有效地完成,请参阅注释。但是,我可以在 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
我创建了一个基于 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[]
缓冲区)
- 不要使用 linq
- pre-allocate记忆
- 不要将其放入 UI 控件中
- 尽可能多地使用内核和线程。
- 使用直接内存。
- 将结果写入文件,而不是使用剪贴板
这可能会更快、更有效地完成,请参阅注释。但是,我可以在 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