javascript - 在JS中有意义:通过foreach在对象集合中搜索值与使用不同的键保留多个集合

标签 javascript node.js

我正在开发某种1:1聊天系统,环境是Node.JS
对于每个国家/地区,都有一个国家/地区空间(大厅),对于每个套接字客户端,都将创建一个js类/对象,并且每个对象都在具有其唯一用户ID的列表中。

即使从不同浏览器选项卡等登录的用户,也会保留此唯一ID。

存储在集合中的每个对象,例如:“连接”(全部),“运算符”(仅运算符),“ {countryISO} _clients”(用户)和引用键是它们的唯一ID。

在某些情况下,我需要通过其套接字ID访问这些连接。
至此,我可以想到两个解决方案。


使用for每个循环查找所需的对象
创建另一个集合,这次使用套接字ID(或其他方法)代替唯一ID。


哪一个有意义?因为在JS中,因为此集合将是参考列表而不是副本,所以感觉像是有意义的(而且看起来很漂亮),但是我不确定。哪一个在内存/性能方面昂贵?

由于我不知道如何创建虚拟(同时)套接字连接,因此无法进行全面的测试。

预期的已连接套接字客户端数:300-1000(取决于一天中的时间)

例如用户:

"qk32d2k":{
 "uid":"qk32d2k",
 "name":"Josh",
 "socket":"{socket.io's socket reference}",
 "role":"user",
 "rooms":["room1"],
 "socketids":["sid1"]
 "country":"us",
 ...
 info:() => { return gatherSomeData(); },
 update:(obj) => { return updateSomeData(obj); },
 send:(data)=>{ /*send data to this user*/ }
}


例如国家集合:

{
 us:{
  "qk32d2k":{"object above."}
  "l33t71":{"another user object."}
 },

 ca:{
  "asd231":{"other user object."}
 }
}

最佳答案

首先选择针对大多数常见访问进行优化的简单设计

绝对没有理想的答案。这些天,CPU速度很快,因此,如果我是我,那么我将以一种简单的存储套接字的机制开始,即使您使用一种蛮力的搜索方式,也可以通过两种方式访问​​所需的套接字。选择优化您期望对性能最常见或最敏感的访问机制的数据结构。

因此,如果您要最多按userID查找,那么我可能会将套接字存储在以userID为键的Map对象中。这将为您提供快速,优化的访问权限,以获取给定用户ID的套接字。

为了通过套接字的其他属性找到套接字,只需逐项迭代Map,直到在其他套接字属性上找到所需的匹配项为止。我可能会使用for/of循环,因为当找到匹配项时,它既快速又容易退出循环(某些事情对于使用MapArray.forEach()对象无法执行)。您显然可以使自己成为一个实用程序函数或方法,该函数或方法将为您执行蛮力查找,并允许您稍后在不更改太多调用代码的情况下修改实现。

以后测量并添加进一步的优化(如果需要显示数据)

然后,一旦达到规模(或在生产前测试中模拟规模),就可以查看系统的性能。如果您有足够的可用空间,就可以完成了-无需进一步检查。如果您执行的某些操作比期望的CPU使用率慢或比期望的CPU使用率高,则可以对系统进行配置并找出时间。您的性能瓶颈很可能会出现在系统中的其他位置,然后您可以专注于系统的那些方面。如果在分析中发现找到所需套接字的线性查找导致了某些问题,那么您可以使用以socketID为键的第二个并行查找Map来优化该类型的查找。

但是,我不建议您这样做,除非您实际表明这是一个问题。在拥有可以证明值得优化的实际指标之前的过早优化,只会增加程序的复杂性,而无须证明它是必需的,甚至无法解决系统中有意义的瓶颈。我们对瓶颈的直觉通常是遥不可及的。出于这个原因,我倾向于选择一个易于实施,维护和使用的智能先行设计,然后,只有当我们拥有可以测量实际性能指标的实际使用情况数据时,我才会花更多时间对其进行优化或调整或使其更复杂以使其更快。

将实现封装在类中

如果将所有操作都封装在一个类中:


向数据结构添加套接字。
从数据结构中删除套接字。
通过用户ID查找
通过socketID查找
对数据结构的任何其他访问


然后,所有调用代码都将通过该类访问此数据结构,您可以在将来的某个时间调整实现(以基于数据进行优化),而无需修改任何调用代码。如果您怀疑将来会对数据的存储或访问方式进行修改或更改,则这种类型的封装非常有用。

如果您仍然担心,请设计一个快速基准测量

我创建了一个快速代码段,测试在1000个元素Map对象中的暴力查找有多长时间(当您希望通过除键以外的其他内容查找它)并将其与索引查找进行比较。

在我的计算机上,蛮力查找(非索引查找)每次查找大约需要0.002549毫秒(这是执行1,000,000次查找时的平均时间。为了进行比较,在同一Map上进行索引查找大约需要0.000017毫秒。因此,您可以节省大约每次查询0.002532毫秒,因此,这是毫秒的分数。



function addCommas(str) {
    var parts = (str + "").split("."),
        main = parts[0],
        len = main.length,
        output = "",
        i = len - 1;
    
    while(i >= 0) {
        output = main.charAt(i) + output;
        if ((len - i) % 3 === 0 && i > 0) {
            output = "," + output;
        }
        --i;
    }
    // put decimal part back
    if (parts.length > 1) {
        output += "." + parts[1];
    }
    return output;
}


let m = new Map();

// populate the Map with objects that have a property that
// you have to do a brute force lookup on

function rand(min, max) {
    return Math.floor((Math.random() * (max - min)) + min)
}

// keep all randoms here just so we can randomly get one
// to try to find (wouldn't normally do this)
// just for testing purposes
let allRandoms = [];

for (let i = 0; i < 1000; i++) {
    let r = rand(1, 1000000);
    m.set(i, {id: r});
    allRandoms.push(r);
}

// create a set of test lookups
// we do this ahead of time so it's not part of the timed
// section so we're only timing the actual brute force lookup
let numRuns = 1000000;
let lookupTests = [];
for (let i = 0; i < numRuns; i++) {
    lookupTests.push(allRandoms[rand(0, allRandoms.length)]);
}

let indexTests = [];
for (let i = 0; i < numRuns; i++) {
    indexTests.push(rand(0, allRandoms.length));
}

// function to brute force search the map to find one of the random items
function findObj(targetVal) {
    for (let [key, val] of m) {
        if (val.id === targetVal) {
            return val;
        }
    }
    return null;
}

let startTime = Date.now();
for (let i = 0; i < lookupTests.length; i++) {

  // get an id from the allRandoms to search for
  let found = findObj(lookupTests[i]);
  if (!found) {
      console.log("!!didn't find brute force target")
  }
}
let delta = Date.now() - startTime;
//console.log(`Total run time for ${addCommas(numRuns)} lookups: ${delta} ms`);
//console.log(`Avg run time per lookup: ${delta/numRuns} ms`);

// Now, see how fast the same number of indexed lookups are
let startTime2 = Date.now();
for (let i = 0; i < indexTests.length; i++) {
    let found = m.get(indexTests[i]);
    if (!found) {
        console.log("!!didn't find indexed target")
    }
}
let delta2 = Date.now() - startTime2;
//console.log(`Total run time for ${addCommas(numRuns)} lookups: ${delta2} ms`);
//console.log(`Avg run time per lookup: ${delta2/numRuns} ms`);

let results = `
Total run time for ${addCommas(numRuns)} brute force lookups: ${delta} ms<br>
Avg run time per brute force lookup: ${delta/numRuns} ms<br>
<hr>
Total run time for ${addCommas(numRuns)} indexed lookups: ${delta2} ms<br>
Avg run time per indexed lookup: ${delta2/numRuns} ms<br>
<hr>
Net savings of an indexed lookup is ${(delta - delta2)/numRuns} ms per lookup
`;
document.body.innerHTML = results;

关于javascript - 在JS中有意义:通过foreach在对象集合中搜索值与使用不同的键保留多个集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58720124/

相关文章:

javascript - 滚动时如何制作动画

javascript - 如何从输入日期获取自定义格式输出?

javascript - req.body 在 Express 应用程序的后路由中未定义

node.js - 无法在 git 部署 Node 服务器上推送到 heroku

node.js - RingCentral MMS 图像公共(public) URI

javascript - Angular 4. XHR 请求中的错误停止了链式可观察量

javascript - 如何分配一个类并切换一个可扩展的菜单项

javascript - 动画元素无法点击

javascript - 在Javascript中将PDF拆分为单独的文件

node.js - pool.request().query(sqlQuery) 受影响的行