让我第一个说这不是我通常做的事情,但出于好奇,我会看看是否有人对如何处理这样的问题有好主意。
我正在开发的应用程序是具有Monty Hall问题的游戏Let's make a Deal
的模拟示例。
我不会详细介绍我的实现,但它或多或少允许用户输入他们想要模拟的游戏数量,然后如果关闭一个选项,这些 x 游戏的玩家将不会切换他们的选择,而如果它被打开,他们将在游戏的每个实例中切换他们的选择。
我的对象生成器如下所示:
const game = function(){
this[0] = null;
this[1] = null;
this[2] = null;
this.pick = Math.floor(Math.random() * 3);
this.correctpick = Math.floor(Math.random() * 3);
this[this.correctpick] = 1;
for (let i=0; i<3; i++){
if ((this[i] !== 1) && (i !== this.pick)){
this.eliminated = i;
break;
}
}
}
const games = arg => {
let ret = [];
for(let i=0; i<arg; i++){
ret.push(new game);
}
return ret;
}
这个结构生成一个数组,我稍后会字符串化,如下所示:
[
{
"0": 1,
"1": null,
"2": null,
"pick": 2,
"correctpick": 0,
"eliminated": 1
},
{
"0": null,
"1": null,
"2": 1,
"pick": 2,
"correctpick": 2,
"eliminated": 0
}
]
尽管游戏的构造函数看起来很草率,但原因是因为我已将其重构为具有尽可能少的函数调用,现在我实际上只在当前时间调用 Math 函数(我删除了任何使代码更易于阅读的辅助函数,以选择性能)。
这个应用程序可以在浏览器和节点(跨平台)中运行,但我已将用户可以传递到游戏函数中的参数固定为 500 万。 超过此时间,进程(或窗口)冻结的时间超过几秒钟,甚至可能崩溃。
如果用户提供了大量数字,我还能做些什么来提高性能? 另外,如果您需要更多信息,我很乐意提供!
谢谢!
明显的性能优化是根本不创建和存储 500 万个对象,从而减轻内存压力。相反,您只会在需要时动态创建对象,然后立即将它们丢弃。我不确定您的应用程序是做什么的,但听起来您希望在使用不同选项评估结果时重复使用相同的游戏实例。在这种情况下,您当然需要存储它们 - 但我建议重新考虑设计并考虑立即评估每个游戏的所有可能选项,仅累积每个选项选择的结果,但不将所有游戏保留在内存中。
除此之外,我建议简化一点:
- 您可以完全删除该循环并使用一些聪明的算术来找到消除的选项:
this.eliminated = this.pick == this.correctpick ? +!this.pick : 3 - this.pick - this.correctpick;
。或者使用简单的查找表this.eliminated = [1, 2, 1, 2, 0, 0, 1, 0, 0][this.pick * 3 + this.correctpick]
。 - 我会避免将数组元素的类型从
null
(引用)更改为1
(数字)。只需将它们保留为整数并用0
初始化元素即可。 - 不要在对象中存储 6 个完全冗余的属性。您只需要其中的 2 个:
pick
和correctpick
- 其他所有内容都可以在需要时从它们动态计算。只有当计算繁重且经常使用结果时,预计算和存储它才是有利的。这两种情况都不是这样,但保持低内存占用很重要(但是,不要对此抱有太大期望)。
不确定您的实现,但您真的需要Array
吗?
只使用结果怎么样(见片段)?
如果它阻止了让你担心的浏览器,也许将工作委托给 Web 工作者是解决方案:请参阅此jsFiddle 以获取此代码段的Webworker 版本。
(() => {
document.querySelector("#doit")
.addEventListener("click", playMontyHall().handleRequest);
function playMontyHall() {
const result = document.querySelector("#result");
const timing = document.querySelector("#timing");
const nOfGames = document.querySelector("#nGames");
const switchDoors = document.querySelector("#switchyn");
// Create a game
const game = (doSwitch) => {
const doors = [0, 1, 2];
const pick = Math.floor(Math.random() * 3);
const correctPick = Math.floor(Math.random() * 3);
const eliminated = doors.filter(v => v !== pick && v !== correctPick)[0];
return {
correctpick: correctPick,
pick: doSwitch ? doors.filter(v => v !== pick && v !== eliminated)[0] : pick,
eliminated: eliminated,
};
};
const getWinner = game => ~~(game.correctpick === game.pick);
// Sum wins using a generator function
const winningGenerator = function* (doSwitch, n) {
let wins = 0;
while (n--) {
wins += getWinner(game(doSwitch));
yield wins;
}
};
// calculate the number of succeeded games
const calculateGames = (nGames, switchAlways) => {
const funNGames = winningGenerator(switchAlways, nGames);
let numberOfWins = 0;
while (nGames--) {
numberOfWins = funNGames.next().value;
}
return numberOfWins;
}
const cleanUp = playOut => {
result.textContent =
"Playing ... (it may last a few seconds)";
timing.textContent = "";
setTimeout(playOut, 0);
};
const report = results => {
timing.textContent = `This took ${
(performance.now() - results.startTime).toFixed(3)} milliseconds`;
result.innerHTML =
`<b>${!results.switchAlways ? "Never s" : "Always s"}witching doors</b>:
${results.winners} winners out of ${results.nGames} games
(${((results.winners/+results.nGames)*100).toFixed(2)}%)`;
};
// (public) handle button click
function clickHandle() {
cleanUp(() => {
const nGames = nOfGames.value || 5000000;
const switchAlways = switchDoors.checked;
report({
switchAlways: switchAlways,
startTime: performance.now(),
winners: calculateGames(nGames, switchAlways),
nGames: nGames
});
});
}
return {
handleRequest: clickHandle
};
}
})();
body {
margin: 2em;
font: normal 12px/15px verdana, arial;
}
#timing {
color: red;
}
<p><input type="number" id="nGames" value="5000000"> N of games</p>
<p><input type="checkbox" id="switchyn"> Always switch doors</p>
<p><button id="doit">Play</button>
<p id="result"></p>
<p id="timing"></p>