当前位置: 首页 > 编程语言 > C#

将单线程应用迁移到多线程,并行执行,蒙特卡洛模拟分享

时间:2023-04-11 11:47:51 C#

将单线程应用迁移到多线程,并行执行,蒙特卡洛模拟我的任务是利用现有的单线程蒙特卡洛模拟,并进行优化.这是c#控制台应用程序,没有数据库访问,它从csv文件加载一次数据并在最后写出,所以它几乎只受CPU限制,也只使用大约50mb的内存。我通过JetbrainsdotTrace分析器运行它。在总执行时间中,大约30%生成均匀随机数,24%将均匀随机数转换为正态分布随机数。基本算法是一大堆嵌套的for循环,中间有随机数调用和矩阵乘法,每次迭代返回一个双精度值,它被添加到结果列表中,这个列表定期排序并测试一些收敛标准(如果选中)acceptable,程序跳出循环并写入结果,否则继续执行到最后。我希望开发人员能够参与:由于我从未编写过任何并行或多线程代码,因此欢迎提供指向上述教程的一些链接。当前应用程序需要2小时进行500,000次迭代,业务需要将其扩展到3,000,000次迭代并且每天调用多次,因此需要进行一些重度优化。特别想听听使用MicrosoftParallelsExtension或AForge.NetParallel的人的意见这需要在生产中相当快所以即使我知道它在那里并发库我们可以看到.net4beta已经出来了我们可以看看在发布后迁移到.net4。目前服务器有.Net2,我已经提交了升级到我的开发箱有的.net3.5SP1的评论。感谢更新我刚刚尝试了Parallel.For实现,但它得出了一些奇怪的结果。单线程:IRandomGeneratorrnd=newMersenneTwister();IDistributiondist=newDiscreteNormalDistribution(discreteNormalDistributionSize);列表结果=newList();for(inti=0;i{results.AddRange(Oblist.Simulate(rnd,dist,n));});在模拟中有很多对rnd的调用。nextUniform(),我想我得到了很多相同的值,这可能吗,因为现在这是并行的?也许ListAddRange调用不是线程安全问题?我知道System.Threading.Collections.BlockingCollection可能值得使用,但它只有Add方法没有AddRange所以我必须查看结果并以线程安全的方式添加。使用Parallel的人的任何见解。太感谢了。我暂时切换到System.Random进行调用,因为在使用我的MersenneTwister实现调用nextUniform时出现异常,也许它不是线程安全的,某些数组使索引超出范围......首先你需要理解为什么您认为使用多线程是一种优化-它不是。仅当您有多个处理器时,使用多线程才能更快地完成您的工作负载,然后至多可用CPU的速度(这称为加速)。传统意义上的工作不是“优化的”(即工作量没有减少——事实上,使用多线程时,由于线程开销,工作总量通常会增加)。因此,在设计应用程序时,您必须找到可以并行或重叠方式完成的工作。可以并行生成随机数(通过在不同的CPU上运行多个RNG),但这也会改变结果,因为你会得到不同的随机数。另一种选择是在一个CPU上生成随机数,而在另一个CPU上生成其他所有内容。这可以为您提供3的最大加速,因为RNG仍将按顺序运行,并且仍需要30%的负载。因此,如果执行此并行化,最终会得到3个线程:线程1运行RNG,线程2运行正态分布,线程3执行其余的模拟。对于这种架构,生产者-消费者架构是最合适的。每个线程将从队列中读取其输入并将其输出产生到另一个队列中。每个队列都应该是阻塞的,因此如果RNG线程落后,规范化线程将自动阻塞,直到有新的nonce可用。为了提高效率,我会跨线程传递100(或更大)数组中的随机数,以避免每个随机数同步。这种方法不需要任何高级线程。只使用常规线程类,没有池,没有库。唯一(不幸的是)标准库中没有的东西是阻塞队列类(System.Collections中的队列类不好)。Codeproject提供了一个看起来合理的实现;可能还有其他人。列表绝对不是线程安全的。请参阅System.Collections.Generic.List文档中的“线程安全”部分。原因是性能:添加线程安全不是免费的。您的随机数实现也不是线程安全的;在这种情况下,多次获得相同的数字正是您所期望的。让我们使用以下简化的rnd.NextUniform()模型来理解发生了什么:从对象的当前状态计算一个伪随机数更新对象的状态,以便下一次调用产生一个不同的数字返回一个伪随机数现在,如果两个线程运行并行执行此方法,可能会发生以下情况:如您所见,您可以执行任何certificateend.NextUniform()工作的推理不再有效,因为这两个线程相互干扰。更糟糕的是,此类错误是时间相关的,并且在某些工作负载或某些系统下很少会“失败”。调试噩梦!一种可能的解决方案是消除状态共享:为每个任务提供自己的随机数生成器,并用另一个种子初始化(假设实例不以某种方式通过静态字段共享状态)。另一个(较差的)解决方案是在MersenneTwister类中创建一个包含锁定对象的字段,如下所示:privateobjectlockObject=newobject();然后在MersenneTwister.NextUniform()实现中使用这个锁:publicdoubleNextUniform(){lock(lockObject){//原始代码在这里}}这将防止两个线程并行执行NextUniform()方法。您可以用类似的方式解决Parallel.For列表:将Simulate调用和AddRange调用分开,然后在AddRange调用周围添加一个锁。我的建议:尽可能避免在并行任务之间共享任何可变状态(如RNG状态)。如果没有共享可变状态,则不会发生线程问题。这也避免了锁定瓶颈:您不希望“并行”任务等待根本无法并行工作的随机数生成器。特别是如果30%的时间花在获取随机数上。将状态共享和锁定限制在无法避免的地方,例如聚合并行执行的结果(例如在AddRange调用中)。线程会变得复杂。您必须将程序分解为逻辑单元,每个逻辑单元都可以在自己的线程上运行,并且您必须处理出现的任何并发问题。ParallelExtensions库应该允许您通过将一些for循环更改为Parallel.For循环来并行化您的程序。如果您想了解它是如何工作的,AndersHejlsberg和JoeDuffy在这段30分钟的视频中提供了很好的介绍:http://channel9.msdn.com/shows/Going+Deep/Programming-in-the-Age-of-Concurrency-Anders-Hejlsberg-and-Joe-Duffy-Concurrent-Programming-with/Thread和ThreadPool顾名思义,ThreadPool就是一个线程池。使用ThreadPool获取线程有一些优势。通过为应用程序提供系统管理的工作线程池,线程池允许您更有效地使用线程。以上就是C#学习教程:将单线程应用迁移到多线程,并行执行,蒙特卡洛模拟。网络收藏不代表立场,如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: