C#字符串拼接性能极低求助:2700万级数据耗时超20小时
字符串拼接性能爆炸?这几个优化点直接救回来
兄弟,你这代码的问题可太典型了——先给你拆解清楚为啥慢到离谱,再上实打实的优化方案:
为啥你的代码慢到20小时都跑不完?
你踩了两个致命的性能坑:
- 字符串不可变性:.NET里的
string是不可变的,每次执行Str1 += part1[x],都会创建一个全新的字符串对象,把原来的内容和新内容复制进去。2700万次拼接意味着2700万次内存分配+复制,内存碎片满天飞,GC都要忙到崩溃。 - 并行线程的共享资源竞争:你在
Parallel.ForEach里直接操作全局的Str1和Str0,这俩变量完全线程不安全。多个线程同时修改时,.NET会自动加锁保证线程安全,结果就是大量线程在等锁,并行直接变成了串行,甚至比纯串行还慢。
优化方案:直接把耗时砍到分钟级
1. 用StringBuilder代替直接拼接,且每个线程用独立实例
StringBuilder是可变的字符缓冲区,拼接时不会每次创建新对象,而且每个线程用自己的StringBuilder,彻底避免线程竞争问题。
2. 预分配StringBuilder容量(关键中的关键)
如果能提前估算出最终字符串的总长度,初始化StringBuilder时指定容量,就能避免它自动扩容时的内存复制操作,性能再上一个大台阶。比如先遍历一次index_choose,计算所有part1[x]的总长度,再给StringBuilder指定初始容量。
3. 线程安全收集结果,最后合并
用线程安全的集合(比如ConcurrentBag<StringBuilder>)收集每个线程的StringBuilder实例,最后把所有内容合并成最终字符串。
优化后的代码示例
// 可选但强烈推荐:提前估算总长度,用于预分配StringBuilder容量 long totalLength1 = 0; long totalLength2 = 0; foreach (var x in index_choose) { totalLength1 += part1[x].Length; totalLength2 += part2[x].Length; } // 线程安全集合,存储每个线程的StringBuilder var sbCollection1 = new ConcurrentBag<StringBuilder>(); var sbCollection2 = new ConcurrentBag<StringBuilder>(); Parallel.ForEach(index_choose, x => { // 每个线程创建独立的StringBuilder,预分配合理容量 var sb1 = new StringBuilder((int)(totalLength1 / Environment.ProcessorCount) + 100); var sb2 = new StringBuilder((int)(totalLength2 / Environment.ProcessorCount) + 100); sb1.Append(part1[x]); sb2.Append(part2[x]); sbCollection1.Add(sb1); sbCollection2.Add(sb2); }); // 合并所有线程的结果 var finalSb1 = new StringBuilder((int)totalLength1); var finalSb2 = new StringBuilder((int)totalLength2); foreach (var sb in sbCollection1) { finalSb1.Append(sb); } foreach (var sb in sbCollection2) { finalSb2.Append(sb); } string total = finalSb1.ToString() + finalSb2.ToString();
额外小提醒
如果之后要恢复UI进度更新,绝对不能在Parallel.ForEach里直接调用progressBar1.PerformStep()或者Application.DoEvents()——UI控件只能在主线程操作,你得用Invoke或者BeginInvoke切换到主线程再更新,不然会出异常还拖慢性能。
内容的提问来源于stack exchange,提问作者Pofak
相关产品推荐
相关产品推荐

