【问题标题】:Allocating array of size 10^5 * 10^5 in c using malloc使用 malloc 在 c 中分配大小为 10^5 * 10^5 的数组
【发布时间】:2014-05-10 18:14:58
【问题描述】:

我两次问了同样的问题(见这里Getting segmentation fault while using malloc)并改进了我的代码。但我无法为更大的 m 和 n 值分配内存。我的代码的核心是:

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int i,j,n,m,p = 0 ;
    int sum[100000] = {0};

    scanf("%d%d%d",&n,&m,&p);

    /*for ( i = 0 ; i < n ; i++ )
    {
        for( j = 0 ; j < m ; j++ )
        {
            a[i][j] = j + 1 ;
        }
    }*/
    int **a = malloc( n * sizeof *a );
    for(i=0; i<n; i++)
    {
        a[i] = malloc( m * sizeof **a); 

           for(j=0; j<m; j++){
            a[i][j] = j + 1 ;

           }
       }



    while ( p-- )
    {
        //printf("I am in while loop\n");
        scanf("%d%d",&i,&j);
        {
            a[i-1][j-1] += 1 ;  
        }
    }

    /*for ( i = 0 ; i < n ; i++ )
    {
        for( j = 0 ; j < m ; j++ )
        {
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }*/
    for ( i = 0 ; i < n ; i++ )
    {
        for( j = 0 ; j < m - 1; j++ )
        {
            p = a[i][j+1] - a[i][j];
            if ( p < 0 )
            {
                sum[i] = -1 ;
                break;
            }
            else
            {
                sum[i]+=p;
            }
            //printf("%d ",p);

        }
    }
    for ( i = 0 ; i < n ; i++ )
    {
        printf("%d\n",sum[i] );
    }

    return 0;
}

约束是: 1 ≤ n, m, p ≤ 10 ^ 5

对于更大的价值,我仍然会遇到运行时错误。我知道我一次分配 10 GB 的内存,但这是问题的要求。我的问题是在 C 中是否可以一次分配这么多内存?如果可能的话,请告诉我。如果没有,那我应该学习 C++。

【问题讨论】:

  • 32 位或 64 位操作系统和程序?
  • If not then should I study C++ 可以分配的内存大小与选择的语言无关...
  • 这很像X-Y Problem。为什么你认为你需要分配这么多难以置信的内存?
  • 只发布整个问题,有人会为您解决,您可以提交它...(当我输入这样一个绝妙的想法时感觉有点错误:))...我怀疑是真实的任务是实现某种sparse matrix,但如果不知道您要在哪个站点上“解决”问题,就很难猜到。
  • 如果您是为了在线比赛而这样做,我怀疑您正在尝试一些可怕的蛮力方法,并且可能有一个更有效的解决方案。

标签: c arrays memory memory-management


【解决方案1】:

除非我误解了这个问题,否则您实际上需要的内存远不止 10GB,不是吗?你想存储 10^10 个数组元素,每个元素都是一个指针的大小。在 64 位系统上,不是每个元素都是 8 字节,而且您需要 80GB 的内存而不是 10GB?

【讨论】:

  • 我同意。解决办法是什么?
【解决方案2】:

您的问题的主要部分是处理(可能)100.000×100.000 个项目的数组。如此大的问题经常出现在编程竞赛中。对于那些坚持简单解决方案的人来说,它们是陷阱。当出现这样的数字时,通常意味着需要在优化数据结构或算法或......问题本身方面付出一些努力。

您没有提供原始问题,并试图让其他人解决分配问题。对我来说,这似乎是一种 X-Y 问题(请参阅 Meta Stack Exchange – What is the XY problem?)——你正在努力分配 100 亿个整数变量,但你没有检查你是否真的需要它们。

让我们看看你的程序试图做什么。首先它读取问题的大小:定义数组大小的值mn,以及要读取的附加输入行数的值p。然后它尝试分配一个数组并用从 1 到 m 的连续自然数填充其行,对应于列号(根据数学惯例,这些值从 1 开始,与 C 语言中的索引不同,它从 0 开始)。

接下来,它加载p 对索引,并为每一对增加数组的指示项。

然后它扫描数组的每一行,计算相邻项之间的差异并验证差异是否为正数或至少为零:如果是,它将计算出的差异累积在sum[]数组中,否则(即如果差异是负数)它放弃该行并将相应的sum[i] 设置为-1 以指示某种“失败”。

最后它打印出计算的总和。

现在,让我们从头开始:您真的需要sum[] 数组吗?嗯,不,你没有。每个总和都是沿着数组的行一次计算的,然后不再使用。这些总和不会被读取或覆盖,它们不会用于任何进一步的计算——它们只是坐在那里等待打印。它们最终会按照计算的顺序打印出来。所以...?所以你不需要存储它们!计算后立即打印每个总和并忘记它:

    for ( i = 0 ; i < n ; i++ )
    {
        int *row = a[i];
        int sum = 0;

        for( j = 0 ; j < m - 1; j++ )
        {
            // calculate and test diff, then...

            diff = row[j+1] - row[j];
            if ( diff < 0 )
                ....
            else
                sum += diff;
        }

        printf("%d\n", sum);    // sum is done
    }

嘿,等等...你看到你的sum 是什么了吗?它是所有差异的总和,差异的总和是......?这是最后一项和第一项之间的区别!所以你不需要迭代它,只需一次计算它,然后决定是否应该取消该值:

    for ( i = 0 ; i < n ; i++ )
    {
        int *row = a[i];
        int sum = row[m-1] - row[0];

        for( j = 0 ; j < m - 1; j++ )
            if ( row[j+1] - row[j] < 0 ) {
                sum = -1;
                break;
            }

        printf("%d\n", sum);
    }

好的,这是一个小优化。现在让我们在代码中后退一步,问下一个问题: — 你真的需要 a 数组吗?你需要它做什么?
— 废话,多么该死的愚蠢问题!你喊,我当然需要!我需要它来存储我的数据!

— 哦,真的吗?...那你为什么需要存储你的数据,我的朋友?
— 叹息...我需要我的数据来计算我的结果。你看不出来吗?

好吧,看,我不能。相反,我可以看到您不需要存储您的数据。至少不是所有您的数据。

你觉得如何,在特殊情况p==0没有a 数组,你能计算出你的结果吗?当然可以! a 数组的初始化方式是,对于每个项目 a[i][j]==j+1,因此您知道每两个相邻项目之间的差异为 1,因此为正数,因此每行中的总和为 (m-1)*1。我们已经计算出,现在不使用a,对吧?

那么p==1呢?在这种情况下,我们将1 添加到数组中的some 项。假设它是a[3][5]。结果如何改变?首先,我们知道只有第 3 行受到影响。接下来,增加位置 5 上的项目会增加a[3][5]-a[3][4] 和减少a[3][6]-a[3][5] 的差异。所以在最后一个阶段我们不需要a——如果我们存储了那些p增量数据项,我们可以立即恢复任何a[i][j]==j+1并应用增量。同样,没有实际使用a 的存储值,对吧?

对于更大的值p 也是类似的——您需要存储的只是在while(p--) 循环中加载的索引对。这将是最多十万件物品,而不是一百亿件物品要存储,因为p 保证不会超过1e5

当然,这个解决方案还有一些额外的困难。但它们肯定是可以解决的。

  • 首先,您不应该扫描每个虚拟a[][] 访问上的所有(可能多达1e5 个)项目,因为这会使整个程序非常缓慢,远远超出可接受的范围。所以那些“+1”补丁应该组织在一些快速访问的数据结构中,例如用于二进制搜索的排序数组,或二进制搜索树,或者可能是一些哈希映射。
  • 接下来,在处理i-th 行时,您需要 该行的补丁。因此,您可以将这些增量按i 分组,这样您就可以轻松找到一个更大的组,其中只包含您需要的部分。
  • 此外,您可以聚合这些数据:当您读取同一对索引时,不要存储另一个 +1 补丁,而是将之前的补丁修改为 +2。这使加载例程有点复杂,因为您需要寻找数据并更新之前存储的内容,或者如果找不到则添加新项目。但是,它可以节省您不必要地扫描树或数组以复制索引。

另一个小的优化:当你扫描一行时,你使用每个项目两次,一次作为被减数,然后作为减数(除了第一个和最后一个项目,它们只使用一次)。所以代码可能看起来像这样:

    for ( i = 0 ; i < n ; i++ )
    {
        // int *row = a[i];  // no a[][], no row anymore

        int item_last  =  m + sumOfPatches( i, m-1 );
        int item_first =  1 + sumOfPatches( i,  0  );
        int sum = item_last - item_first;

        int item_prev = item_first;

        for( j = 0 ; j < m - 1; j++ )
        {
            // a[i][j+1] was originally initialized to (j+2)

            int item_next = j+2 + sumOfPatches( i, j+1 )
            if ( item_next - item_prev < 0 )
            {
                sum = -1;
                break;
            }

            // the item which is 'next' in current iteration
            // will be a 'prev' in the next iteration

            item_prev = item_next;
        }

        printf("%d\n", sum);
    }

作为最终优化,您可以从 item_next 计算中删除 j+2 项,因为这部分在每次迭代时都会增加 1,因此当减去 item_next - item_prev 时,它会减少到恒定的差异 (j+2)-(j+1) == 1

源代码的相关部分如下所示:

        int patch_last = sumOfPatches( i, m-1 );
        int patch_first = sumOfPatches( i,  0  );
        int sum = (m + patch_last) - (1 + patch_first);

        int patch_prev = patch_first;

        for( j = 0 ; j < m - 1; j++ )
        {
            int patch_next = sumOfPatches( i, j+1 );
            if ( 1 + patch_next - patch_prev < 0 )
            {
                sum = -1;
                break;
            }

            patch_prev = patch_next;
        }

HTH(虽然这是一个“非常非常迟的答案”......)

【讨论】:

    【解决方案3】:

    你应该测试 malloc 是否返回 null,当它不能分配时它会返回。

    使用 64 位操作系统和 64 位编译器,分配 10 GB 应该是可能的。尽管在分配内存时填充内存,但您必须至少有 10 GB 或虚拟内存(交换空间算作内存,虽然这会很慢)。

    【讨论】:

    • 如果我在线编译呢。
    • 那是你的主要问题。获取 Ubuntu 64 位,它是免费的,非常适合 C 和 C++。
    • @jahan 除非您与管理远程服务器的任何人达成协议,否则这是非常粗鲁的做法,如果有效,可能会破坏使用同一服务器的其他人的体验。
    • 那么我怀疑在线服务会为您提供 10 GB 的 RAM...您可以尝试 printf("%i", sizeof(int*)) 检查指针的大小。如果是 4,你在 32 位模式下编译,理论上你可以获得的最大内存是 4 GB,实际上是 2 GB,但我猜该服务甚至会在之前被切断......
    • @jahan 如果您在线编译,您至少应该确保运行它的机器有超过 10GB 的内存。 (随机在线代码提交服务器不会让你分配 10GB 内存...)
    猜你喜欢
    • 2020-06-26
    • 2019-09-28
    • 2020-01-12
    • 1970-01-01
    • 2017-11-21
    • 2022-11-11
    • 1970-01-01
    • 2014-03-10
    • 1970-01-01
    相关资源
    最近更新 更多