《C 语言程序设计》似乎在之前的某篇文章里辣评了一下,这里不说了。但不得不承认的是,这门课发挥了它应有的价值:虽然学校自己的教授方法落后,作业考试大部分只是 intellectual masturbation,用的 IDE 是 Dev C++ 这种。为了让自己写得舒服点,我迫使自己掌握了 VS Code / VS + Git 的工作流,并且顺便熟悉了 Linux 操作系统等等。
所以,如果没有这么一套课程,我没有那么强烈的动力去做这些事情——这些事情其实就是 MIT 那套大名鼎鼎的 The Missing Semester of Your CS Education 课程会讲授的内容,但是我一直都没机会(或者说不敢/不想)上手做一遍。
var watch = new Stopwatch();
stopwatch.Start();
for (ulong i = 0; i < N; i++)
{
x = rnd.NextDouble();
y = rnd.NextDouble();
if (x * x + y * y <= 1)
{
circleCount++;
}
}
double PI = 4 * (double)circleCount / N;
watch.Stop();
Console.WriteLine("Pi = {0}, time = {1}ms", PI, watch.ElapsedMilliseconds);
设置采样数为 5000000000,得到 Pi = 3.1415527664, time = 37026ms。为了计算这个稳定于 3.1415 以上的结果,花费了 37 秒。
多线程
现代 CPU 具有多个物理核心,可以把它们利用起来。用 ThreadLocal<Random>() 为各个线程单独生成随机数。
using System.Threading;
using System.Threading.Tasks;
voidParallelEst()
{
// 全局计数器long totalCircleCount = 0;
var watch = new Stopwatch();
ThreadLocal<Random> threadRnd = new ThreadLocal<Random>(() => new Random(Guid.NewGuid().GetHashCode())); //各线程通过唯一 GUID 生成哈希值,防止随机样本序列重复
watch.Start();
// 循环并行化
Parallel.For(
0,
(long)N,
() => 0UL,
(i, loopState, localCount) => // i: 当前迭代,localCount: 当前线程的局部计数器
{
Random rnd = threadRnd.Value;
double x = rnd.NextDouble();
double y = rnd.NextDouble();
if (x * x + y * y <= 1)
{
localCount++;
}
return localCount;
},
localCount => // 累积局部计数器到全局计数器
{
Interlocked.Add(ref totalCircleCount, (long)localCount);
}
);
threadRnd.Dispose();
double PI = 4 * (double)totalCircleCount / N;
watch.Stop();
Console.WriteLine("Pi = {0}, time = {1}ms", PI, watch.ElapsedMilliseconds);
}
以下是运行时间:
1 线程
24 线程
36412 ms
6348 ms
多线程的确占用起来了:
taskmgr
踩坑
之前是这么写的,结果并行化反而慢了 10 倍:
voidParallelEst()
{
ulong circleCount = 0;
var watch = new Stopwatch();
ThreadLocal<Random> threadRnd = new ThreadLocal<Random>(() => new Random(Guid.NewGuid().GetHashCode()));
watch.Start();
Parallel.For(0, (long)N, i =>
{
Random rnd = threadRnd.Value;
double x = rnd.NextDouble();
double y = rnd.NextDouble();
if (x * x + y * y <= 1)
{
Interlocked.Increment(ref circleCount); // 锁在线程循环里面
}
});
threadRnd.Dispose();
double PI = 4 * (double)circleCount / N;
watch.Stop();
Console.WriteLine("Pi = {0}, time = {1}ms", PI, watch.ElapsedMilliseconds);
}