NodeJS 在大型for循环中比较元素时出现JavaScript堆内存不足问题

6pp0gazn  于 2023-01-08  发布在  Node.js
关注(0)|答案(2)|浏览(104)

我有一个巨大的JSON文件要处理,其中包含大约15,00,000个JSON对象。我正在执行一些搜索操作,其中我使用了两个for循环来比较对象值。
下面是一个例子:

const data = [
 {
  "slug": "vertical-lift-module-market",
  "id": 68055,
  "related_reports_updated": {
  "sub_categories": [
    {
      "slug": "audience-analytics-market",
      "id": 66684,
      "short_title": "Audience Analytics Market"
    },
    {
      "slug": "mobile-wallet-market",
      "id": 68830,
      "short_title": "Mobile Wallet Market"
    }
  }
},
{
"slug": "united-states-real-estate-services---growth-trends-and-forecast-2022-- -2027",
"id": 68056,
"related_reports_updated": {
  "sub_categories": [
    {
      "slug": "canada-real-estate-services-market---growth-trends-and-forecast-2020---2025",
      "id": 68051,
      "short_title": "Canada Real Estate Services Market"
    },
    {
      "slug": "germany-real-estate-services-market--growth-trends-and-forecast-2020---2025",
      "id": 68054,
      "short_title": "Germany Real Estate Services Market"
    },
  }
 },
 {
  ...
 }  
]
//This data holds 15,00,000 JSON objects

我尝试做的是比较一个对象的slug和其他对象的sub_categories数组中可用的slug。如果存在,则创建一个对象并将其推入result数组,然后发送result数组。

const result = [];

for(var i=0;i<data.length;i++) {
  
   for(var j=0;j<data.length;j++) {

        //Comparing operation
  }

} 

console.log(result);

但运行后,有时它给我这个错误:

[41955:0x523ce90]   162238 ms: Mark-sweep (reduce) 4096.9 (4102.7) -> 4096.9 (4104.7) 
MB, 3481.7 / 0.4 ms  (average mu = 0.092, current mu = 0.000) allocation failure scavenge might not succeed

<--- JS stacktrace --->

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
1: 0xa3ac10 node::Abort() [node]
2: 0x970199 node::FatalError(char const*, char const*) [node]
3: 0xbba58e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) 
[node]
4: 0xbba907 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char 
const*, bool) [node]
5: 0xd76b25  [node]
6: 0xd776af  [node]
7: 0xd854eb v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, 
v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
8: 0xd890ac v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, 
v8::internal::AllocationType, v8::internal::AllocationOrigin, 
v8::internal::AllocationAlignment) [node]
9: 0xd5778b v8::internal::Factory::NewFillerObject(int, bool, 
v8::internal::AllocationType, v8::internal::AllocationOrigin) [node]
10: 0x109fd4f v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, 
v8::internal::Isolate*) [node]
11: 0x1448f59  [node]

已中止(核心转储)
为了消除这个错误,我甚至尝试使用node --max-old-space-size=4096 index.js来最大化节点进程的内存。
但是我仍然得到同样的问题,有没有其他的方法来解决这个问题,并得到理想的结果?

w1jd8yoj

w1jd8yoj1#

让我们假设任何元素都有单词和数字。

  • js中的双精度64位数占用8个字节 *
  • 5个字母的单词约为10个字节**

如果单个元素有18个字节,则

1.5e6 elements is 2.7e7 bytes = 27 MB

独立于代码:

const obj = {};
             
         if(data[i].name == data[j].name) {
              obj.name = data[i].name;
         }

你在双循环中推送到result数组,这些循环中的任何一个都要经过1.5e6元素,所以总结一下,你要给result添加很多元素,在这个例子中:x一米三纳米一x x一米四纳米一x = x一米五纳米一x。
现在,让我们计算result的大小
x一米七氮一x *(x一米八氮一x + x一米九氮一x)= x一米十氮一x
我们知道,1 GB = 1 000 000 000字节= 1e9字节1 TB = 1e12字节
因此,在您的情况下,您需要40 TB来保存您的result
当然,你必须重新考虑你想要实现的算法。

  • 每个数字实际上平均占用9.7个字节。source
    **我们可以假设每个字符2个字节,但也可以是5个或更多字节source
siotufzp

siotufzp2#

如果您使用不太复杂的算法,不添加重复值,那么它将把数量少得多的元素推入结果数组。

let dictionary = {};

// O(n) to build dictionary of all parent slugs
for(let i=0;i<n;i++)
{
   dictionary[data[i].slug]=1; // hashing
}

// O(n*k) to check dictionary instead of doing O(n*n*k) for brute-force
for(let i=0;i<n;i++)
for(let j=0;j<data[i].sub_cat.length;j++)
{
    // O(1)
    if(data[i].sub_cat[j].slug in dictionary)
        dictionary[data[i].sub_cat[j].slug]++; // 2 => will go to result
}

// add to result O(n)
for (const [key, value] of Object.entries(dictionary)) {
  if(value==2)
      result.push(key);
}

因为它也会与自身进行比较,就像元素5与元素5一样,它仍然会在比较中重复,但不会作为结果项重复。因此,您可能需要根据所需的算法添加额外的比较。

相关问题