首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >蒙特卡罗Pi

蒙特卡罗Pi
EN

Code Review用户
提问于 2014-12-24 15:48:06
回答 2查看 3.4K关注 0票数 14

这是如此的帖子的启发下,我研究了一种基于仿真的Java8计算Pi的方法。

我使用了类似的任务来学习在CUDA和Intel Xeon Phi处理器上的并行编程。这些系统更适合并行编程,但我还是倾向于将其应用于“常规”Java。

维基百科有一小部分显示了,并包括以下图形:

下面的代码执行上述模拟:

代码语言:javascript
复制
 import java.util.Arrays;
 import java.util.Random;

 /**
  * Approximate the value of Pi by using a Monte-Carlo simulation for the area of a circle of radius 1.
  *
  * @author rolf
  *
  */
 public class PiGuess {


     private static final ThreadLocal<Random> LOCAL_RANDOM = new ThreadLocal<Random>() {
         protected Random initialValue() {
             return new Random();
         };
     };

     private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

     /**
      * Split a number of samples as evenly as possible over the number of available processors.
      * @param samples the samples to split
      * @return an array containing the number of samples to process on each processor.
      */
     private static final long[] apportion(final long samples) {
         int core = CPU_COUNT;
         final long[] portions = new long[core];
         long remaining = samples;

         while (core > 0) {
             final long part = (remaining - 1 + core) / core;
             core--;
             portions[core] = part;
             remaining -= part;
         }
         return portions;
     }

     /**
      * Calculate the approximate area of a circle (radius 1.0) based on a sample system on a single quadrant of the circle.
      * A parallel mechanism is used to improve performance.
      * @param samples the number of samples to take
      * @return the area of the circle.
      */
     public static final double sampleCircleArea(final long samples) {

         /*
         Monte-Carlo simulation for the area of a circle.

         A circle of radius 1 just fits in a square of sides 2.
         In one quadrant of the square (area 1 by 1) we have a quarter circle
         If we put the center of the circle at origin 0,0, and then randomly sample points
         in that quadrant, we can tell whether that point is in the circle if the ray from the
         origin is shorter than the radius of the circle.
         If the point is at (x,y), then the ray is the 'hypotenuse' (using Pythagoras).

         We know the area of the quadrant, we can sample millions of points in the quadrant,
         and we can calculate a ratio of the quadrant's area that is inside the circle.

         This sampled area, multiplied by 4, gives the area of the circle.

         */

         // how many samples to process in each thread.
         long[] counts = apportion(samples);

         // add up how many samples appear in the circle
         long inside = Arrays.stream(counts).parallel().map(s -> samplePortion(s)).sum();

         // convert the quadrant area back to the circle area.
         return (4.0 * inside) / samples;
     }

     /**
      * Internal sampling method that counts the number of input samples that are inside the circle too.
      * @param samples the samples to calculate
      * @return the count of samples that are inside the circle.
      */
     private static final long samplePortion(final long samples) {
         final Random rand = LOCAL_RANDOM.get();
         long inside = 0;
         for (int i = 0; i < samples; i++) {
             if (isInside(rand)) {
                 inside++;
             }
         }
         return inside;
     }

     /**
      * The core test for each sample, does a random point in the quadrant lie inside the circle.
      * @param rand the source for the random circle.
      * @return true if the random sample is inside the circle.
      */
     private static final boolean isInside(final Random rand) {
         final double x = rand.nextDouble();
         final double y = rand.nextDouble();
         return x * x + y * y <= 1.0;
     }


     public static void main(String[] args) {
         double[] calculations = new double[100];
         for (int i = 0; i < calculations.length; i++) {
             double calc = sampleCircleArea(100000);
             calculations[i] = calc;
             System.out.printf("Loop %d guesses Pi at %.5f%n", i, calc);
         }
         System.out.printf("Overall calculation is %.5f%n", Arrays.stream(calculations).average().getAsDouble());
     }

 }

当我使用1,000,000个示例运行它时,我以以下输出结束:

循环97在3.14128环98猜测Pi在3.14102环99猜测Pi在3.14142总体计算为3.14154

我特别关心的问题是:

  1. 性能能提高吗?
  2. Java 8机制是否被适当地使用?我错过了什么简化吗?
  3. 还有其他的观察吗?
EN

回答 2

Code Review用户

回答已采纳

发布于 2014-12-24 16:47:51

一般来说,代码和文档看起来都很好,所以本文将着重于在每个方法的基础上进行一些小的优化。

使用ThreadLocal.withInitial

您可以使用以下方法设置LOCAL_RANDOM

代码语言:javascript
复制
private static final ThreadLocal<Random> LOCAL_RANDOM = ThreadLocal.withInitial(Random::new);

您需要给出一个Supplier<Random>作为参数,这正是Random::new

约简逻辑涉及apportion

在我看来,太多的事情就是这个方法,需要有一种更干净的方法来写它。一个例子是:尽管它仍然没有消除所有的增量和递减操作:

代码语言:javascript
复制
/**
 * Split a number of samples as evenly as possible over the number of available processors.
 * 
 * This method will for example split 77 over 4 as [20, 19, 19, 19]
 * 
 * @param samples the samples to split
 * @return an array containing the number of samples to process on each processor.
 */
private static final long[] apportion(final long samples) {
    int cores = CPU_COUNT;
    long[] portions = new long[cores];

    //calculate minimum amount of samples per core
    long minSamples = samples / cores;
    Arrays.setAll(portions, index -> minSamples);

    //calculate remaining samples after evenly dividing the minimum amount over all cores
    long remaining = samples - (minSamples * cores);

    //split the remaining samples over the cores        
    for (int coreId = 0; remaining > 0; coreId++) {
        portions[coreId]++;
        remaining--;
    }

    return portions;
}

流化samplePortion

这个建议可能是一个趣味问题,但我相信用函数式编程变体编写的一段代码比一些涉及循环的计算更容易理解,更重要的是它不容易出错,因为您不可能不正确地实现count()方法!

代码语言:javascript
复制
/**
 * Internal sampling method that counts the number of input samples that are inside the circle too.
 * @param samples the samples to calculate
 * @return the count of samples that are inside the circle.
 */
private static final long samplePortion(final long samples) {
    final Random random = LOCAL_RANDOM.get();
    return LongStream.range(0, samples)
        .filter(sample -> isInside(random))
        .count();
}

该解决方案还减少了使用的代码行,因此理论上应该更易于维护。

在数字文本中使用下划线

为了澄清,您可以在main方法中编写以下内容:

代码语言:javascript
复制
double calc = sampleCircleArea(100_000);

注意100_000 the 100000

票数 10
EN

Code Review用户

发布于 2014-12-24 21:03:51

总的来说,对于给定的任务,一切看起来都是非常合适的,我同意斯基维的观点。

如果您的类PiGuess未被实例化,则使其成为最终结果,并添加一个私有构造函数。

代码语言:javascript
复制
public final class PiGuess {
    private PiGuess() {}
}

这就引出了下一个问题:您的代码不是面向对象的。如果你的目标是实现一个原型应用程序的话,就不一定是这样了。

如果您还想使用OOP,请忘记我的第一个注释,并考虑以下几点:从方法中移除静态关键字,向PiGuess添加一个构造函数,该构造函数以样本大小和计算数为参数,并添加一个公共方法,该方法基本完成主要方法现在所做的工作(构造函数中没有计算,这不是它们的用途)。

除此之外,double[] calculations = new double[100];可能是最终的。

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/74780

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档