问题描述
我创建了一个基于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
- 预分配内存
- 请勿将其放入UI控件
- 使用尽可能多的内核和线程。
- 使用直接内存。
- 将结果写入文件,而不是使用剪贴板
这可能会更快,更有效地完成,请参见注释。但是,我可以在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个索引,因为数学很简单,如果可以将线程与内核匹配,则结果会好一点